#include "../../ClassSpellsDamage.h"
#include "../../ClassSpellsCoeff.h"
#include "SpellHistory.h"

class UnbreakableWillTest : public TestCaseScript
{
public:
    UnbreakableWillTest() : TestCaseScript("talents priest unbreakable_will") { }

    //"Increases your chance to resist Stun, Fear, and Silence effects by an additional 15%"
    class UnbreakableWillTestImpt : public TestCase
    {
        /*
        Bugs: Not sure, feels like the special attacks hit is wrong
        */
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* warlock = SpawnPlayer(CLASS_WARLOCK, RACE_HUMAN);
            TestPlayer* warrior = SpawnPlayer(CLASS_WARRIOR, RACE_HUMAN);
            TestPlayer* enemyPriest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);

            LearnTalent(priest, Talents::Priest::UNBREAKABLE_WILL_RNK_5);
            float const talentResistFactor = 15.f;
            float const expectedSpellResist = talentResistFactor + 3.f; // 3% is required to be spell hit capped in PvP
            float const expectedMeleeResist = talentResistFactor; // 5% melee cap is in SPELL_MISS_MISS so we don't need to count it

            ASSERT_INFO("Fear");
            TEST_SPELL_HIT_CHANCE(warlock, priest, ClassSpells::Warlock::FEAR_RNK_3, expectedSpellResist, SPELL_MISS_RESIST, [](Unit* caster, Unit* victim) {
                victim->ClearDiminishings();
                victim->RemoveAurasDueToSpell(ClassSpells::Warlock::FEAR_RNK_3);
            });
            ASSERT_INFO("Concussion blow");
            TEST_SPELL_HIT_CHANCE(warrior, priest, Talents::Warrior::CONCUSSION_BLOW_RNK_1, expectedMeleeResist, SPELL_MISS_RESIST, [](Unit* caster, Unit* victim) {
                victim->ClearDiminishings();
                victim->RemoveAurasDueToSpell(Talents::Warrior::CONCUSSION_BLOW_RNK_1);
            });
            ASSERT_INFO("Silence");
            TEST_SPELL_HIT_CHANCE(enemyPriest, priest, ClassSpells::Priest::SILENCE_RNK_1, expectedSpellResist, SPELL_MISS_RESIST, [](Unit* caster, Unit* victim) {
                victim->ClearDiminishings();
                victim->RemoveAurasDueToSpell(ClassSpells::Priest::SILENCE_RNK_1);
            });
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<UnbreakableWillTestImpt>();
    }
};

class PriestWandSpecializationTest : public TestCaseScript
{
public:
    PriestWandSpecializationTest() : TestCaseScript("talents priest wand_specialization") { }

    //"Increases your damage with Wands by 25%."
    class WandSpecializationTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            Creature* dummy = SpawnCreature();

            EQUIP_NEW_ITEM(priest, 28783); // Eredar Wand of Obliteration, 177 - 330 Shadow Damage

            LearnTalent(priest, Talents::Priest::WAND_SPECIALIZATION_RNK_5);
            float const talentFactor = 1.25f;

            uint32 const expectedWandMin = 177 * talentFactor;
            uint32 const expectedWandMax = 330 * talentFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::WAND, expectedWandMin, expectedWandMax, false);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<WandSpecializationTestImpt>();
    }
};

class SilentResolve : public TestCaseScript
{
public:
    SilentResolve() : TestCaseScript("talents priest silent_resolve") { }

    //"Reduces the threat generated by your Holy and Discipline spells by 20% and reduces the chance your spells will be dispelled by 20%"
    class SilentResolveTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            _location.MoveInFront(_location, 5.0f);
            TestPlayer* enemy = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Creature* dummy = SpawnCreature();

            LearnTalent(priest, Talents::Priest::SILENT_RESOLVE_RNK_5);
            float const talentReductionFactor = 1.0f - 0.2f;
            float const healThreatFactor = 0.5f * talentReductionFactor;
            const float dispelTalentFactor = 20.f;

            // Threat of Discipline and Holy
            // PWS: Omen uses half threat to calculate at cast (like other heals)
            // More info about at cast threat: http://www.wowhead.com/forums&topic=94784/power-word-shield-and-threat
            TEST_THREAT(priest, dummy, ClassSpells::Priest::POWER_WORD_SHIELD_RNK_12, healThreatFactor, true);
            // Chastise: "Very low threat", Omen affects a half factor.
            TEST_THREAT(priest, dummy, ClassSpells::Priest::CHASTISE_RNK_6, 0.5f * talentReductionFactor);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::CIRCLE_OF_HEALING_RNK_5, healThreatFactor, true);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::DESPERATE_PRAYER_RNK_8, healThreatFactor, true);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::FLASH_HEAL_RNK_9, healThreatFactor, true);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::GREATER_HEAL_RNK_7, healThreatFactor, true);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::HEAL_RNK_4, healThreatFactor, true);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::HOLY_FIRE_RNK_9, talentReductionFactor);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::LESSER_HEAL_RNK_3, healThreatFactor, true);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::PRAYER_OF_HEALING_RNK_6, healThreatFactor, true);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::STARSHARDS_RNK_8, talentReductionFactor);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::RENEW_RNK_12, healThreatFactor, true);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::SMITE_RNK_10, talentReductionFactor);
            //Mind blast not affected
            TEST_THREAT(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, 1.0f, false);

            // Binding Heal: "Low threat", Omen & DTM affects it a half factor.
            GroupPlayer(priest, ally);
            ally->SetHealth(1);
            priest->SetHealth(1);
            dummy->EngageWithTarget(ally);
            dummy->EngageWithTarget(priest);
            EnableCriticals(priest, false);
            TEST_CAST(priest, ally, ClassSpells::Priest::BINDING_HEAL_RNK_1, SPELL_CAST_OK, TRIGGERED_FULL_MASK);
            auto [healMinBHPriest, healMaxBHPriest] = GetHealingPerSpellsTo(priest, priest, ClassSpells::Priest::BINDING_HEAL_RNK_1, false, 1);
            auto [healMinBHAlly, healMaxBHAlly] = GetHealingPerSpellsTo(priest, ally, ClassSpells::Priest::BINDING_HEAL_RNK_1, false, 1);
            float actualPriestThreat = dummy->GetThreatManager().GetThreat(priest);
            const float expectedBindingHealThreat = 0.5f * (healMinBHPriest + healMinBHAlly) * healThreatFactor;
            ASSERT_INFO("Priest should have %f threat but has %f.", expectedBindingHealThreat, actualPriestThreat);
            TEST_ASSERT(Between<float>(actualPriestThreat, expectedBindingHealThreat - 0.1f, expectedBindingHealThreat + 0.1f));
            // Prayer of Mending
            dummy->ForceMeleeHitResult(MELEE_HIT_NORMAL);
            TEST_CAST(priest, ally, ClassSpells::Priest::PRAYER_OF_MENDING_RNK_1, SPELL_CAST_OK, TriggerCastFlags(TRIGGERED_FULL_MASK | TRIGGERED_IGNORE_SPEED));
            dummy->AttackerStateUpdate(ally, BASE_ATTACK);
            dummy->AttackStop();
            const float expectedPoMThreat = actualPriestThreat + ClassSpellsDamage::Priest::PRAYER_OF_MENDING_RNK_1 * healThreatFactor;
            actualPriestThreat = dummy->GetThreatManager().GetThreat(priest);
            ASSERT_INFO("Priest should have %f threat but has %f.", expectedPoMThreat, actualPriestThreat);
            TEST_ASSERT(Between<float>(actualPriestThreat, expectedPoMThreat - 0.1f, expectedPoMThreat + 0.1f));
            //cleanup
            ally->KillSelf();
            priest->RemoveAurasDueToSpell(ClassSpells::Priest::PRAYER_OF_MENDING_RNK_1_BUFF);

            // Resist dispell of all spells
            // Disc
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::DIVINE_SPIRIT_RNK_5, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::ELUNES_GRACE_RNK_1, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::FEAR_WARD_RNK_1, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::INNER_FIRE_RNK_7, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, Talents::Priest::INNER_FOCUS_RNK_1, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::LEVITATE_RNK_1, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::PAIN_SUPPRESSION_RNK_1, dispelTalentFactor + 65.f);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::POWER_INFUSION_RNK_1, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::POWER_WORD_FORTITUDE_RNK_7, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::POWER_WORD_SHIELD_RNK_12, dispelTalentFactor, [](Unit* caster, Unit* victim) {
                victim->RemoveAurasDueToSpell(ClassSpells::Priest::WEAKENED_SOUL);
            });
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::PRAYER_OF_FORTITUDE_RNK_3, dispelTalentFactor);

            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::PRAYER_OF_SPIRIT_RNK_2, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, ClassSpells::Priest::STARSHARDS_RNK_8, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::SYMBOL_OF_HOPE_RNK_1, dispelTalentFactor);
            // Holy
            TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, ClassSpells::Priest::HOLY_FIRE_RNK_9, dispelTalentFactor);
            // Shadow
            TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, ClassSpells::Priest::DEVOURING_PLAGUE_RNK_7, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, ClassSpells::Priest::HEX_OF_WEAKNESS_RNK_7, dispelTalentFactor);
            //Not dispellable //TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, ClassSpells::Priest::MIND_FLAY_RNK_7, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::PRAYER_OF_SHADOW_PROTECTION_RNK_2, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::SHADOW_PROTECTION_RNK_4, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, ClassSpells::Priest::PSYCHIC_SCREAM_RNK_4, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::SHADOW_GUARD_RNK_7, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, ClassSpells::Priest::TOUCH_OF_WEAKNESS_RNK_7, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, ClassSpells::Priest::SILENCE_RNK_1, dispelTalentFactor);
            // Talents procs - We suppose that this talent affects other talents procs such as:
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, Talents::Priest::MARTYRDOM_RNK_2_TRIGGER, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, Talents::Priest::INSPIRATION_RNK_3_TRIGGER, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, Talents::Priest::FOCUSED_WILL_RNK_3_TRIGGER, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, priest, enemy, Talents::Priest::HOLY_CONCENTRATION_RNK_3_TRIGGER, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, Talents::Priest::SHADOW_WEAVING_RNK_5_PROC, dispelTalentFactor);
            TEST_DISPEL_RESIST_CHANCE(priest, enemy, enemy, Talents::Priest::BLACKOUT_RNK_5_TRIGGER, dispelTalentFactor);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<SilentResolveTestImpt>();
    }
};

class ImprovedPowerWordFortitudeTest : public TestCaseScript
{
public:

    ImprovedPowerWordFortitudeTest() : TestCaseScript("talents priest improved_power_word_fortitude") { }

    //"Increases the effect of your Power Word : Fortitude and Prayer of Fortitude spells by 30%"
    class ImprovedPowerWordFortitudeTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* warrior = SpawnPlayer(CLASS_WARRIOR, RACE_ORC);

            LearnTalent(priest, Talents::Priest::IMPROVED_POWER_WORD_FORTITUDE_RNK_2);
            float const talentFactor = 1.3f;

            uint32 powerWordFortitudeBonus = 79 * talentFactor;
            uint32 expectedMaxHealth = warrior->GetMaxHealth() + powerWordFortitudeBonus * 10;

            // Power Word: Fortitude
            TEST_CAST(priest, warrior, ClassSpells::Priest::POWER_WORD_FORTITUDE_RNK_7);
            TEST_ASSERT(warrior->GetMaxHealth() == expectedMaxHealth);

            // Prayer of Fortitude
            GroupPlayer(priest, warrior);
            TEST_CAST(priest, warrior, ClassSpells::Priest::PRAYER_OF_FORTITUDE_RNK_3, SPELL_CAST_OK, TRIGGERED_FULL_MASK);
            TEST_ASSERT(warrior->GetMaxHealth() == expectedMaxHealth);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedPowerWordFortitudeTestImpt>();
    }
};

class ImprovedPowerWordShieldTest : public TestCaseScript
{
public:
    ImprovedPowerWordShieldTest() : TestCaseScript("talents priest improved_power_word_shield") { }

    //"Increases the damage absorbed by your Power Word : Shield by 15%"
    class ImprovedPowerWordShieldTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* rogue = SpawnPlayer(CLASS_ROGUE, RACE_ORC);

            LearnTalent(priest, Talents::Priest::IMPROVED_POWER_WORD_SHIELD_RNK_3);
            float const talentFactor = 1.15f;

            int32 expectedAbsorb = ClassSpellsDamage::Priest::POWER_WORD_SHIELD_RNK_12 * talentFactor;
            TEST_CAST(priest, priest, ClassSpells::Priest::POWER_WORD_SHIELD_RNK_12);

            // Step 1 - test theoritical amount
            Aura const* shield = priest->GetAura(ClassSpells::Priest::POWER_WORD_SHIELD_RNK_12);
            TEST_ASSERT(shield != nullptr);
            AuraEffect const* absorbEffect = shield->GetEffect(EFFECT_0);
            int32 const absorbAmount = absorbEffect->GetAmount();
            ASSERT_INFO("absorbAmount %u != expectedAbsorb %u", absorbAmount, expectedAbsorb);
            TEST_ASSERT(absorbAmount == expectedAbsorb);

            // Step 2 - Test with real damage
            priest->SetFullHealth();
            auto AI = rogue->GetTestingPlayerbotAI();
            rogue->ForceMeleeHitResult(MELEE_HIT_NORMAL);
            int32 totalDamage = 0;
            for (uint32 i = 0; i < 150; i++)
            {
                rogue->AttackerStateUpdate(priest, BASE_ATTACK);
                auto damageToTarget = AI->GetMeleeDamageDoneInfo(priest);

                TEST_ASSERT(damageToTarget->size() == i + 1);
                auto& data = damageToTarget->back();

                for (uint8 j = 0; j < MAX_ITEM_PROTO_DAMAGES; j++)
                {
                    totalDamage += data.damageInfo.Damages[j].Damage;
                    totalDamage += data.damageInfo.Damages[j].Resist;
                    totalDamage += data.damageInfo.Damages[j].Absorb;
                }
                totalDamage += data.damageInfo.Blocked;

                if (totalDamage < expectedAbsorb)
                {
                    TEST_HAS_AURA(priest, ClassSpells::Priest::POWER_WORD_SHIELD_RNK_12); //if this fails, shield did not absorb enough 
                    continue;
                }

                TEST_HAS_NOT_AURA(priest, ClassSpells::Priest::POWER_WORD_SHIELD_RNK_12);
                uint32 expectedHealth = priest->GetMaxHealth() - (totalDamage - expectedAbsorb);
                TEST_ASSERT(priest->GetHealth() == expectedHealth);
                break;
            }
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedPowerWordShieldTestImpt>();
    }
};

class MartyrdomTest : public TestCaseScript
{
public:
    MartyrdomTest() : TestCaseScript("talents priest martyrdom") { }

    //"Gives you a 100% chance to gain the Focused Casting effect that lasts for 6sec after being the victim of a melee or ranged critical strike. The Focused Casting effect prevents you from losing casting time when taking damage while casting Priest spells and increases resistance to Interrupt effects by 20%"
    class MartyrdomTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* shaman = SpawnPlayer(CLASS_SHAMAN, RACE_DRAENEI);

            LearnTalent(priest, Talents::Priest::MARTYRDOM_RNK_2);
            float const talentResistPushbackFactor = 100.f;
            float const talentResistInterruptFactor = 20.f;
            float const expectedResist = talentResistInterruptFactor + 3.f; // 3% is required to be spell hit capped in PvP

            shaman->ForceMeleeHitResult(MELEE_HIT_CRIT);
            shaman->AttackerStateUpdate(priest, BASE_ATTACK);
            shaman->AttackStop();
            TEST_AURA_MAX_DURATION(priest, Talents::Priest::MARTYRDOM_RNK_2_TRIGGER, Seconds(6));
            shaman->ResetForceMeleeHitResult();

            // No pushback
            TEST_PUSHBACK_RESIST_CHANCE(priest, priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, talentResistPushbackFactor);

            // 20% resist to interrupt
            TEST_SPELL_HIT_CHANCE(shaman, priest, ClassSpells::Shaman::EARTH_SHOCK_RNK_8, expectedResist, SPELL_MISS_RESIST, [](Unit* caster, Unit* victim) {
                victim->AddAura(Talents::Priest::MARTYRDOM_RNK_2_TRIGGER, victim);
                victim->CastSpell(victim, ClassSpells::Priest::GREATER_HEAL_RNK_7, TRIGGERED_IGNORE_POWER_AND_REAGENT_COST);
            });
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<MartyrdomTestImpt>();
    }
};

class AbsolutionTest : public TestCaseScript
{
public:
    //"Reduces the mana cost of your Dispel Magic, Cure Disease, Abolish Disease and Mass Dispel spells by 15%."
    AbsolutionTest() : TestCaseScript("talents priest absolution") { }

    class AbsolutionTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            LearnTalent(priest, Talents::Priest::ABSOLUTION_RNK_3);
            float const talentFactor = 1 - 0.15f;

            TEST_POWER_COST(priest, ClassSpells::Priest::DISPEL_MAGIC_RNK_2, POWER_MANA, 366 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::CURE_DISEASE_RNK_1, POWER_MANA, 314 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::ABOLISH_DISEASE_RNK_1, POWER_MANA, 314 * talentFactor);
            uint32 const baseMana = priest->GetMaxPower(POWER_MANA) - priest->GetManaBonusFromIntellect();
            float const massDispelMana = baseMana * 0.33f;
            TEST_POWER_COST(priest, ClassSpells::Priest::MASS_DISPEL_RNK_1, POWER_MANA, massDispelMana * talentFactor);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<AbsolutionTestImpt>();
    }
};

class InnerFocusTest : public TestCaseScript
{
public:
    InnerFocusTest() : TestCaseScript("talents priest inner_focus") { }

    //When activated, reduces the mana cost of your next spell by 100% and increases its critical effect chance by 25% if it is capable of a critical effect.
    class InnerFocusTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Creature* dummy = SpawnCreature();
            float const talentFactor = 25.f;
            float const expectedHolySpellCritChance = priest->GetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + SPELL_SCHOOL_HOLY) + talentFactor;

            // CD
            TEST_CAST(priest, priest, ClassSpells::Priest::INNER_FOCUS_RNK_1);
            TEST_HAS_AURA(priest, ClassSpells::Priest::INNER_FOCUS_RNK_1);
            TEST_POWER_COST(priest, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, POWER_MANA, uint32(0));
            TEST_CAST(priest, dummy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10);
            TEST_HAS_COOLDOWN(priest, ClassSpells::Priest::INNER_FOCUS_RNK_1, Minutes(3));

            // Crit chance
            TEST_SPELL_CRIT_CHANCE(priest, dummy, ClassSpells::Priest::SMITE_RNK_10, expectedHolySpellCritChance, [](Unit* caster, Unit* target) {
                caster->AddAura(ClassSpells::Priest::INNER_FOCUS_RNK_1, caster);
            });
            TEST_SPELL_CRIT_CHANCE(priest, priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, expectedHolySpellCritChance, [](Unit* caster, Unit* target) {
                caster->AddAura(ClassSpells::Priest::INNER_FOCUS_RNK_1, caster);
            });
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<InnerFocusTestImpt>();
    }
};

class MeditationTest : public TestCaseScript
{
public:
    MeditationTest() : TestCaseScript("talents priest meditation") { }

    //"Allows 30% of your mana regeneration to continue while casting."
    class MeditationTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            float const talentFactor = 0.3f;
            float const expectedManaRegen = priest->GetFloatValue(PLAYER_FIELD_MOD_MANA_REGEN) * talentFactor;

            LearnTalent(priest, Talents::Priest::MEDITATION_RNK_3);
            ASSERT_INFO("Priest should have %f mana regen but has %f.", expectedManaRegen, priest->GetFloatValue(PLAYER_FIELD_MOD_MANA_REGEN_INTERRUPT));
            TEST_ASSERT(Between<float>(priest->GetFloatValue(PLAYER_FIELD_MOD_MANA_REGEN_INTERRUPT), expectedManaRegen - 0.1f, expectedManaRegen + 0.1));
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<MeditationTestImpt>();
    }
};

class ImprovedInnerFireTest : public TestCaseScript
{
public:
    ImprovedInnerFireTest() : TestCaseScript("talents priest improved_inner_fire") { }

    //"Increases the armor bonus of your Inner Fire spell by 30%"
    class ImprovedInnerFireTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            float const talentFactor = 1.3f;
            uint32 const innerFireBonus = 1580 * talentFactor;
            uint32 const expectedArmor = priest->GetArmor() + innerFireBonus;

            LearnTalent(priest, Talents::Priest::IMPROVED_INNER_FIRE_RNK_3);
            TEST_CAST(priest, priest, ClassSpells::Priest::INNER_FIRE_RNK_7);
            ASSERT_INFO("Priest should have %u armor but has %u", expectedArmor, priest->GetArmor());
            TEST_ASSERT(priest->GetArmor() == expectedArmor);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedInnerFireTestImpt>();
    }
};

class MentalAgilityTest : public TestCaseScript
{
public:
    MentalAgilityTest() : TestCaseScript("talents priest mental_agility") { }

    //"Reduces the mana cost of your instant cast spells by 10%."
    class MentalAgilityTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            float const talentFactor = 1 - 0.1f;
            uint32 const baseMana = priest->GetMaxPower(POWER_MANA) - priest->GetManaBonusFromIntellect();

            LearnTalent(priest, Talents::Priest::MENTAL_AGILITY_RNK_5);
            WaitNextUpdate();
            // Discipline
            TEST_POWER_COST(priest, ClassSpells::Priest::DISPEL_MAGIC_RNK_2, POWER_MANA, baseMana * 0.14f * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::DIVINE_SPIRIT_RNK_5, POWER_MANA, 680 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::ELUNES_GRACE_RNK_1, POWER_MANA, baseMana * 0.03f * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::FEAR_WARD_RNK_1, POWER_MANA, baseMana * 0.03f * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::FEEDBACK_RNK_6, POWER_MANA, 705 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::INNER_FIRE_RNK_7, POWER_MANA, 375 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::LEVITATE_RNK_1, POWER_MANA, 100 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::POWER_INFUSION_RNK_1, POWER_MANA, baseMana * 0.16f * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::POWER_WORD_FORTITUDE_RNK_7, POWER_MANA, 700 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::POWER_WORD_SHIELD_RNK_12, POWER_MANA, 600 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::PRAYER_OF_FORTITUDE_RNK_3, POWER_MANA, 1800 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::PRAYER_OF_SPIRIT_RNK_2, POWER_MANA, 1800 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::SYMBOL_OF_HOPE_RNK_1, POWER_MANA, 15 * talentFactor);
            // Holy
            TEST_POWER_COST(priest, ClassSpells::Priest::ABOLISH_DISEASE_RNK_1, POWER_MANA, baseMana * 0.12f * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::CHASTISE_RNK_6, POWER_MANA, 300 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::CIRCLE_OF_HEALING_RNK_5, POWER_MANA, 450 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::CURE_DISEASE_RNK_1, POWER_MANA, baseMana * 0.12f * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::HOLY_NOVA_RNK_7, POWER_MANA, 875 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::PRAYER_OF_MENDING_RNK_1, POWER_MANA, 390 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::RENEW_RNK_12, POWER_MANA, 450 * talentFactor);
            // Shadow
            TEST_POWER_COST(priest, ClassSpells::Priest::DEVOURING_PLAGUE_RNK_7, POWER_MANA, 1145 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::FADE_RNK_7, POWER_MANA, 330 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::HEX_OF_WEAKNESS_RNK_7, POWER_MANA, 295 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::MIND_SOOTHE_RNK_4, POWER_MANA, 120 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::PRAYER_OF_SHADOW_PROTECTION_RNK_2, POWER_MANA, 1620 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::PSYCHIC_SCREAM_RNK_4, POWER_MANA, 210 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::SHADOW_PROTECTION_RNK_4, POWER_MANA, 810 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2, POWER_MANA, 309 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, POWER_MANA, 575 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::SHADOWFIEND_RNK_1, POWER_MANA, baseMana * 0.06f * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::SHADOW_GUARD_RNK_7, POWER_MANA, 270 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::TOUCH_OF_WEAKNESS_RNK_7, POWER_MANA, 235 * talentFactor);
            TEST_POWER_COST(priest, ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1, POWER_MANA, baseMana * 0.02f * talentFactor);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<MentalAgilityTestImpt>();
    }
};

class ImprovedManaBurnTest : public TestCaseScript
{
public:
    ImprovedManaBurnTest() : TestCaseScript("talents priest improved_mana_burn") { }

    //"Reduces the casting time of your Mana Burn spell by 1 sec."
    class ImprovedManaBurnTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            LearnTalent(priest, Talents::Priest::IMPROVED_MANA_BURN_RNK_2);
            TEST_CAST_TIME(priest, ClassSpells::Priest::MANA_BURN_RNK_7, 2000);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedManaBurnTestImpt>();
    }
};

class MentalStrengthTest : public TestCaseScript
{
public:
    MentalStrengthTest() : TestCaseScript("talents priest mental_strength") { }

    //"Increases your maximum mana by 10%."
    class MentalStrengthTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            float const talentFactor = 1.1f;

            uint32 expectedMaxMana = priest->GetMaxPower(POWER_MANA) * talentFactor;

            LearnTalent(priest, Talents::Priest::MENTAL_STRENGTH_RNK_5);
            ASSERT_INFO("Priest has %u max MP, should have %u.", priest->GetMaxPower(POWER_MANA), expectedMaxMana);
            TEST_ASSERT(Between<uint32>(priest->GetMaxPower(POWER_MANA), expectedMaxMana - 1, expectedMaxMana + 1));
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<MentalStrengthTestImpt>();
    }
};

class DivineSpiritTest : public TestCaseScript
{
public:
    DivineSpiritTest() : TestCaseScript("talents priest divine_spirit") { }

    //"Holy power infuses the target, increasing their Spirit by 50 for 30min."
    class DivineSpiritTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            uint32 expectedSpirit = priest->GetStat(STAT_SPIRIT) + 50;

            TEST_CAST(priest, priest, ClassSpells::Priest::DIVINE_SPIRIT_RNK_5);
            TEST_ASSERT(priest->GetStat(STAT_SPIRIT) == expectedSpirit);

            TEST_AURA_MAX_DURATION(priest, ClassSpells::Priest::DIVINE_SPIRIT_RNK_5, Minutes(30));

            TEST_POWER_COST(priest, ClassSpells::Priest::DIVINE_SPIRIT_RNK_5, POWER_MANA, 680);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<DivineSpiritTestImpt>();
    }
};

class ImprovedDivineSpiritTest : public TestCaseScript
{
public:
    ImprovedDivineSpiritTest() : TestCaseScript("talents priest improved_divine_spirit") { }

    //"Your Divine Spirit and Prayer of Spirit spells also increase the target's spell damage and healing by an amount equal to 10% of their total Spirit."
    class ImprovedDivineSpiritTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            float const spirit = priest->GetStat(STAT_SPIRIT) + 50;

            EQUIP_NEW_ITEM(priest, 34335); // Hammer of Sanctification -- 183 SP & 550 BH
            uint32 const bonusHeal = priest->GetInt32Value(PLAYER_FIELD_MOD_HEALING_DONE_POS);
            TEST_ASSERT(bonusHeal == 550);
            uint32 const spellPower = priest->GetInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + SPELL_SCHOOL_SHADOW);
            TEST_ASSERT(spellPower == 183);

            float const talentFactor = 0.1f;
            uint32 expectedBH = bonusHeal + spirit * talentFactor;
            uint32 expectedSP = spellPower + spirit * talentFactor;

            LearnTalent(priest, Talents::Priest::IMPROVED_DIVINE_SPIRIT_RNK_2);
            TEST_CAST(priest, priest, ClassSpells::Priest::DIVINE_SPIRIT_RNK_5);
            TEST_ASSERT(priest->GetInt32Value(PLAYER_FIELD_MOD_HEALING_DONE_POS) == int32(expectedBH));
            TEST_ASSERT(priest->GetInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + SPELL_SCHOOL_SHADOW) == int32(expectedSP));

            TEST_CAST(priest, priest, ClassSpells::Priest::PRAYER_OF_SPIRIT_RNK_2, SPELL_CAST_OK, TRIGGERED_FULL_MASK);
            TEST_ASSERT(priest->GetInt32Value(PLAYER_FIELD_MOD_HEALING_DONE_POS) == int32(expectedBH));
            TEST_ASSERT(priest->GetInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + SPELL_SCHOOL_SHADOW) == int32(expectedSP));
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedDivineSpiritTestImpt>();
    }
};

class FocusedPowerTest : public TestCaseScript
{
public:
    FocusedPowerTest() : TestCaseScript("talents priest focused_power") { }

    //"Your Smite, Mind Blast and Mass Dispel spells have an additional 4% chance to hit. In addition, your Mass Dispel cast time is reduced by 1 sec."
    class FocusedPowerTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Creature* boss = SpawnBoss();

            LearnTalent(priest, Talents::Priest::FOCUSED_POWER_RNK_2);
            float const hitTalentFactor = 4.0f;
            float const hitChance = 16.0f - hitTalentFactor;

            // Hit chance
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::SMITE_RNK_10, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::MIND_BLAST_RNK_11, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::MASS_DISPEL_RNK_1, hitChance, SPELL_MISS_RESIST);

            // Mass Dispel Cast Time
            TEST_CAST_TIME(priest, ClassSpells::Priest::MASS_DISPEL_RNK_1, 500); //from 1500
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<FocusedPowerTestImpt>();
    }
};

class ForceOfWillTest : public TestCaseScript
{
public:
    ForceOfWillTest() : TestCaseScript("talents priest force_of_will") { }

    //"Increases your spell damage by 5% and the critical strike chance of your offensive spells by 5%"
    class ForceOfWillTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Creature* dummy = SpawnCreature();

            float const talentDamageFactor = 1.05f;
            float const talentCritFactor = 5.f;

            float const expectedSpellCritChance = priest->GetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + SPELL_SCHOOL_SHADOW) + talentCritFactor;

            LearnTalent(priest, Talents::Priest::FORCE_OF_WILL_RNK_5);
            // SP
            // Devouring Plague
            uint32 const dpTickAmount = 8;
            uint32 const expectedDPDoT = dpTickAmount * floor(ClassSpellsDamage::Priest::DEVOURING_PLAGUE_RNK_7_TICK * talentDamageFactor);
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::DEVOURING_PLAGUE_RNK_7, expectedDPDoT, false);
            // Starshards
            uint32 const starshardsTickAmount = 5;
            uint32 const expectedStarshardsDoT = starshardsTickAmount * floor(ClassSpellsDamage::Priest::STARSHARDS_RNK_8_TICK * talentDamageFactor);
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::STARSHARDS_RNK_8, expectedStarshardsDoT, false);
            // Smite
            uint32 const expectedSmiteMin = ClassSpellsDamage::Priest::SMITE_RNK_10_MIN * talentDamageFactor;
            uint32 const expectedSmiteMax = ClassSpellsDamage::Priest::SMITE_RNK_10_MAX * talentDamageFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::SMITE_RNK_10, expectedSmiteMin, expectedSmiteMax, false);
            // Holy Fire -- Direct
            uint32 const expectedHolyFireMin = ClassSpellsDamage::Priest::HOLY_FIRE_RNK_9_MIN * talentDamageFactor;
            uint32 const expectedHolyFireMax = ClassSpellsDamage::Priest::HOLY_FIRE_RNK_9_MAX * talentDamageFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::HOLY_FIRE_RNK_9, expectedHolyFireMin, expectedHolyFireMax, false);
            // Holy Fire -- Dot
            uint32 const holyFireTickAmount = 5;
            uint32 const holyFireDoT = holyFireTickAmount * floor(ClassSpellsDamage::Priest::HOLY_FIRE_RNK_9_TICK * talentDamageFactor);
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::HOLY_FIRE_RNK_9, holyFireDoT, true);
            // Holy Nova
            uint32 const expectedHolyNovaMin = ClassSpellsDamage::Priest::HOLY_NOVA_RNK_7_MIN_LVL_70 * talentDamageFactor;
            uint32 const expectedHolyNovaMax = ClassSpellsDamage::Priest::HOLY_NOVA_RNK_7_MAX_LVL_70 * talentDamageFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::HOLY_NOVA_RNK_7, expectedHolyNovaMin, expectedHolyNovaMax, false);
            // Mind Blast
            uint32 const expectedMindBlastMin = ClassSpellsDamage::Priest::MIND_BLAST_RNK_11_MIN * talentDamageFactor;
            uint32 const expectedMindBlastMax = ClassSpellsDamage::Priest::MIND_BLAST_RNK_11_MAX * talentDamageFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, expectedMindBlastMin, expectedMindBlastMax, false);
            // SwD
            uint32 const expectedSwDMin = ClassSpellsDamage::Priest::SHADOW_WORD_DEATH_RNK_2_MIN * talentDamageFactor;
            uint32 const expectedSwDMax = ClassSpellsDamage::Priest::SHADOW_WORD_DEATH_RNK_2_MAX * talentDamageFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2, expectedSwDMin, expectedSwDMax, false);
            // SwP
            uint32 const swpTickAmount = 6;
            uint32 const expectedSwPDoT = swpTickAmount * floor(ClassSpellsDamage::Priest::SHADOW_WORD_PAIN_RNK_10_TICK * talentDamageFactor);
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, expectedSwPDoT, false);
            // Vampiric Touch
            uint32 const vtTickAmount = 5;
            uint32 const expectedVTDoT = vtTickAmount * floor(ClassSpellsDamage::Priest::VAMPIRIC_TOUCH_RNK_3_TICK * talentDamageFactor);
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, expectedVTDoT, false);
            // Mind Flay
            uint32 const mfTickAmount = 3;
            uint32 const expectedMindFlayTick = ClassSpellsDamage::Priest::MIND_FLAY_RNK_7_TICK * talentDamageFactor;
            TEST_CHANNEL_DAMAGE(priest, dummy, ClassSpells::Priest::MIND_FLAY_RNK_7, mfTickAmount, expectedMindFlayTick);

            // Crit
            TEST_SPELL_CRIT_CHANCE(priest, dummy, ClassSpells::Priest::SMITE_RNK_10, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, dummy, ClassSpells::Priest::HOLY_FIRE_RNK_9, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, dummy, ClassSpells::Priest::HOLY_NOVA_RNK_7, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, dummy, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2, expectedSpellCritChance);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ForceOfWillTestImpt>();
    }
};

class FocusedWillTest : public TestCaseScript
{
public:
    FocusedWillTest() : TestCaseScript("talents priest focused_will") { }

    //"After taking a critical hit you gain the Focused Will effect, reducing all damage taken by 4% and increasing healing effects on you by 10%. Stacks up to 3 times. Lasts 8sec."
    class FocusedWillTestImpt : public TestCase
    {
        uint32 const MAX_STACK = 3;

        void RefreshProcWithMaxStacks(TestPlayer* priest)
        {
            priest->RemoveAurasDueToSpell(Talents::Priest::FOCUSED_WILL_RNK_3_TRIGGER);
            for (uint8 i = 0; i < MAX_STACK; i++)
                priest->AddAura(Talents::Priest::FOCUSED_WILL_RNK_3_TRIGGER, priest);
        }

        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* shaman = SpawnPlayer(CLASS_SHAMAN, RACE_DRAENEI);

            LearnTalent(priest, Talents::Priest::FOCUSED_WILL_RNK_3);
            float const talentDamageTakenFactorPerStack = 0.04f;
            float const talentHealingBoostPerStack = 0.1f;

            //procs on melee attack
            shaman->ForceMeleeHitResult(MELEE_HIT_CRIT);
            for (uint8 i = 0; i < MAX_STACK * 2; i++) //more than MAX_STACK to make sure stack amount is correctly limited
                shaman->AttackerStateUpdate(priest, BASE_ATTACK);
            shaman->AttackStop();
            shaman->ResetForceMeleeHitResult();

            //duration + stack limit
            TEST_AURA_MAX_DURATION(priest, Talents::Priest::FOCUSED_WILL_RNK_3_TRIGGER, Seconds(8));
            Aura* aura = priest->GetAura(Talents::Priest::FOCUSED_WILL_RNK_3_TRIGGER);
            TEST_ASSERT(aura != nullptr);
            TEST_ASSERT(aura->GetStackAmount() == int32(MAX_STACK));

            // Damage reduced: melee MH & Earth Shock
            EQUIP_NEW_ITEM(shaman, 34165); // Fang of Kalecgos
            WaitNextUpdate();
            float const talentReduction = 1 - MAX_STACK * talentDamageTakenFactorPerStack;
            { //melee damage reduction
                auto[minMelee, maxMelee] = CalcMeleeDamage(shaman, priest, BASE_ATTACK);
                minMelee *= talentReduction;
                maxMelee *= talentReduction;
                RefreshProcWithMaxStacks(priest);
                TEST_MELEE_DAMAGE(shaman, priest, BASE_ATTACK, minMelee, maxMelee, false);
            }
            { //spell damage reduction
                uint32 const minEarthShock = ClassSpellsDamage::Shaman::EARTH_SHOCK_RNK_8_MIN_LVL_70 * talentReduction;
                uint32 const maxEarthShock = ClassSpellsDamage::Shaman::EARTH_SHOCK_RNK_8_MAX_LVL_70 * talentReduction;
                RefreshProcWithMaxStacks(priest);
                TEST_DIRECT_SPELL_DAMAGE(shaman, priest, ClassSpells::Shaman::EARTH_SHOCK_RNK_8, minEarthShock, maxEarthShock, false);
            }

            // Healing increased
            RefreshProcWithMaxStacks(priest);
            float const talentBoost = 1 + 3 * talentHealingBoostPerStack;
            uint32 const minGreaterHeal = ClassSpellsDamage::Priest::GREATER_HEAL_RNK_7_MIN * talentBoost;
            uint32 const maxGreaterHeal = ClassSpellsDamage::Priest::GREATER_HEAL_RNK_7_MAX * talentBoost;
            TEST_DIRECT_HEAL(priest, priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, minGreaterHeal, maxGreaterHeal, false);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<FocusedWillTestImpt>();
    }
};

class PowerInfusionTest : public TestCaseScript
{
public:
    PowerInfusionTest() : TestCaseScript("talents priest power_infusion") { }

    //"Infuses the target with power, increasing spell casting speed by 20% and reducing the mana cost of all spells by 20%. Lasts 15sec."
    class PowerInfusionTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            float const castingSpeedIncrease = 1.2f;
            float const manaCostReduction = 0.8f;

            TEST_CAST(priest, priest, ClassSpells::Priest::POWER_INFUSION_RNK_1);
            TEST_AURA_MAX_DURATION(priest, ClassSpells::Priest::POWER_INFUSION_RNK_1, Seconds(15));
            TEST_HAS_COOLDOWN(priest, ClassSpells::Priest::POWER_INFUSION_RNK_1, Minutes(3));

            // Haste: X% haste means to cast X additional spells in the time it would normally take to cast 100 spells
            uint32 const greaterHealCastTime = 3 * IN_MILLISECONDS;
            uint32 const expectedGreaterHealCastTime = greaterHealCastTime * 100 / uint32(100 * castingSpeedIncrease);
            TEST_CAST_TIME(priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, expectedGreaterHealCastTime);

            // Mana reduction
            uint32 const expectedGreaterHealManaCost = 825 * manaCostReduction;
            TEST_POWER_COST(priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, POWER_MANA, expectedGreaterHealManaCost);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<PowerInfusionTestImpt>();
    }
};

class ReflectiveShieldTest : public TestCaseScript
{
public:
    ReflectiveShieldTest() : TestCaseScript("talents priest reflective_shield") { }

    //"Causes 50% of the damage absorbed by your Power Word: Shield to reflect back at the attacker. This damage causes no threat."
    class ReflectiveShieldTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Position spawn;
            spawn.MoveInFront(_location, 5.f);
            TestPlayer* mage = SpawnPlayer(CLASS_MAGE, RACE_TROLL, 70, spawn);
            spawn.MoveInFront(spawn, 5.f);
            TestPlayer* enemyMage = SpawnPlayer(CLASS_MAGE, RACE_HUMAN, 70, spawn);
            enemyMage->DisableRegeneration(true);

            LearnTalent(priest, Talents::Priest::REFLECTIVE_SHIELD_RNK_5);

            // Patch 2.4: Reflective Shield will no longer break crowd control effects.
            // Enemy casts on Priest and gets polymorphed before the spell hits the reflective shield
            TEST_CAST(priest, priest, ClassSpells::Priest::POWER_WORD_SHIELD_RNK_12, SPELL_CAST_OK, TRIGGERED_FULL_MASK);
            TEST_HAS_AURA(priest, ClassSpells::Priest::POWER_WORD_SHIELD_RNK_12);
            uint32 enemyMageStartingHealth = enemyMage->GetHealth();
            FORCE_CAST(enemyMage, priest, ClassSpells::Mage::ICE_LANCE_RNK_1, SPELL_MISS_NONE, TRIGGERED_FULL_MASK);
            //ice lance is still in flight and ally mage polymorph enemy mage
            FORCE_CAST(mage, enemyMage, ClassSpells::Mage::POLYMORPH_RNK_4, SPELL_MISS_NONE, TriggerCastFlags(TRIGGERED_FULL_MASK | TRIGGERED_IGNORE_SPEED));
            //give some time for the spell to fly and hit
            WaitNextUpdate();
            Wait(2000);
            //make sure spell did hit:
            GetDamagePerSpellsTo(enemyMage, priest, ClassSpells::Mage::ICE_LANCE_RNK_1, {}, 1);
            //should still have polymorph
            TEST_HAS_AURA(enemyMage, ClassSpells::Mage::POLYMORPH_RNK_4);
            enemyMage->RemoveAurasDueToSpell(ClassSpells::Mage::POLYMORPH_RNK_4);

            // Reflects 50% of damage
            TEST_HAS_AURA(priest, ClassSpells::Priest::POWER_WORD_SHIELD_RNK_12);
            enemyMageStartingHealth = enemyMage->GetHealth();
            FORCE_CAST(enemyMage, priest, ClassSpells::Mage::SCORCH_RNK_9, SPELL_MISS_NONE, TriggerCastFlags(TRIGGERED_FULL_MASK | TRIGGERED_IGNORE_SPEED));
            auto[dealtMin, dealtMax] = GetDamagePerSpellsTo(enemyMage, priest, ClassSpells::Mage::SCORCH_RNK_9, false, 1);
            TEST_ASSERT(dealtMin == dealtMax);
            uint32 enemyMageExpectedHealth = enemyMageStartingHealth - floor(dealtMin * 0.5f);
            ASSERT_INFO("Mage should have %u HP but has %u HP.", enemyMageExpectedHealth, enemyMage->GetHealth());
            TEST_ASSERT(enemyMage->GetHealth() == enemyMageExpectedHealth);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ReflectiveShieldTestImpt>();
    }
};

class EnlightenmentTest : public TestCaseScript
{
public:
    EnlightenmentTest() : TestCaseScript("talents priest enlightenment") { }

    //"Increases your total Stamina, Intellect and Spirit by 5%."
    class EnlightenmentTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            float const talentFactor = 1.05f;

            uint32 expectedSpirit = priest->GetStat(STAT_SPIRIT) * talentFactor;
            uint32 expectedStamina = priest->GetStat(STAT_STAMINA) * talentFactor;
            uint32 expectedIntellect = priest->GetStat(STAT_INTELLECT) * talentFactor;

            LearnTalent(priest, Talents::Priest::ENLIGHTENMENT_RNK_5);

            TEST_ASSERT(priest->GetStat(STAT_SPIRIT) == expectedSpirit);
            TEST_ASSERT(priest->GetStat(STAT_STAMINA) == expectedStamina);
            TEST_ASSERT(priest->GetStat(STAT_INTELLECT) == expectedIntellect);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<EnlightenmentTestImpt>();
    }
};

class PainSuppressionTest : public TestCaseScript
{
public:
    PainSuppressionTest() : TestCaseScript("talents priest pain_suppression") { }

    //"Instantly reduces a friendly target's threat by 5%, reduces all damage taken by 40% and increases resistance to Dispel mechanics by 65% for 8sec."
    //Note: The 5% is handled by a linked spell
    class PainSuppressionTestImpt : public TestCase
    {
    public:


        void RefreshPainSuppression(TestPlayer* priest)
        {
            priest->RemoveAurasDueToSpell(ClassSpells::Priest::PAIN_SUPPRESSION_RNK_1);
            priest->AddAura(ClassSpells::Priest::PAIN_SUPPRESSION_RNK_1, priest);
        }

        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* shaman = SpawnPlayer(CLASS_SHAMAN, RACE_DRAENEI);

            float const talentDamageTakenFactor = 0.6f;
            float const talentThreatFactor = 0.95f;
            float const talentDispelResistFactor = 65.f;

            TEST_CAST(priest, priest, ClassSpells::Priest::PAIN_SUPPRESSION_RNK_1);
            TEST_AURA_MAX_DURATION(priest, ClassSpells::Priest::PAIN_SUPPRESSION_RNK_1, Seconds(8));
            TEST_HAS_COOLDOWN(priest, ClassSpells::Priest::PAIN_SUPPRESSION_RNK_1, Minutes(2));
            priest->RemoveAurasDueToSpell(ClassSpells::Priest::PAIN_SUPPRESSION_RNK_1);
            priest->GetSpellHistory()->ResetAllCooldowns();

            // Reduces threat by 5%
            Creature* dummy = SpawnCreature();
            FORCE_CAST(priest, dummy, ClassSpells::Priest::SMITE_RNK_10, SPELL_MISS_NONE, TRIGGERED_FULL_MASK);
            float const expectedThreat = dummy->GetThreatManager().GetThreat(priest) * talentThreatFactor;
            TEST_CAST(priest, priest, ClassSpells::Priest::PAIN_SUPPRESSION_RNK_1);
            WaitNextUpdate();
            ASSERT_INFO("Priest should have %f threat but has %f.", expectedThreat, dummy->GetThreatManager().GetThreat(priest));
            TEST_ASSERT(Between<float>(dummy->GetThreatManager().GetThreat(priest), expectedThreat - 0.1f, expectedThreat + 0.1f));

            // Reduces all damage taken by 40%
            EQUIP_NEW_ITEM(shaman, 34165); // Fang of Kalecgos
            WaitNextUpdate();

            auto[minMelee, maxMelee] = CalcMeleeDamage(shaman, priest, BASE_ATTACK);
            minMelee *= talentDamageTakenFactor;
            maxMelee *= talentDamageTakenFactor;
            RefreshPainSuppression(priest);
            TEST_MELEE_DAMAGE(shaman, priest, BASE_ATTACK, minMelee, maxMelee, false);
            uint32 const minEarthShock = ClassSpellsDamage::Shaman::EARTH_SHOCK_RNK_8_MIN_LVL_70 * talentDamageTakenFactor;
            uint32 const maxEarthShock = ClassSpellsDamage::Shaman::EARTH_SHOCK_RNK_8_MAX_LVL_70 * talentDamageTakenFactor;
            RefreshPainSuppression(priest);
            TEST_DIRECT_SPELL_DAMAGE(shaman, priest, ClassSpells::Shaman::EARTH_SHOCK_RNK_8, minEarthShock, maxEarthShock, false);

            // Increases resistance to dispell by 65%
            TEST_DISPEL_RESIST_CHANCE(priest, priest, shaman, ClassSpells::Priest::PAIN_SUPPRESSION_RNK_1, talentDispelResistFactor);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<PainSuppressionTestImpt>();
    }
};

class SearingLightTest : public TestCaseScript
{
public:
	SearingLightTest() : TestCaseScript("talents priest searing_light") { }

    //"Increases the damage of your Smite and Holy Fire spells by 10%"
	class SearingLightTestImpt : public TestCase
	{
	public:


		void Test() override
		{
			TestPlayer* priest = SpawnRandomPlayer(CLASS_PRIEST);
            Creature* dummy = SpawnCreature();

            LearnTalent(priest, Talents::Priest::SEARING_LIGHT_RNK_2);
            float const talentFactor = 1.1f;

			// Smite
			uint32 const expectedSmiteMin = ClassSpellsDamage::Priest::SMITE_RNK_10_MIN * talentFactor;
			uint32 const expectedSmiteMax = ClassSpellsDamage::Priest::SMITE_RNK_10_MAX * talentFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::SMITE_RNK_10, expectedSmiteMin, expectedSmiteMax, false);

			// Holy Fire
			uint32 const expectedHolyFireMin = ClassSpellsDamage::Priest::HOLY_FIRE_RNK_9_MIN * talentFactor;
			uint32 const expectedHolyFireMax = ClassSpellsDamage::Priest::HOLY_FIRE_RNK_9_MAX * talentFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::HOLY_FIRE_RNK_9, expectedHolyFireMin, expectedHolyFireMax, false);
            uint32 const expectedHolyFireDot = 5.0f * floor(ClassSpellsDamage::Priest::HOLY_FIRE_RNK_9_TOTAL * talentFactor / 5.0f);
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::HOLY_FIRE_RNK_9, expectedHolyFireDot, false);
		}
	};

	std::unique_ptr<TestCase> GetTest() const override
	{
		return std::make_unique<SearingLightTestImpt>();
	}
};

class HealingFocusTest : public TestCaseScript
{
public:
    HealingFocusTest() : TestCaseScript("talents priest healing_focus") { }

    //"Gives you a 70% chance to avoid interruption caused by damage while casting any healing spell"
    class HealingFocusTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            LearnTalent(priest, Talents::Priest::HEALING_FOCUS_RNK_2);
            float const talentResistPushbackFactor = 70.f;

            // 70% pushback resist
            TEST_PUSHBACK_RESIST_CHANCE(priest, ally, ClassSpells::Priest::BINDING_HEAL_RNK_1, talentResistPushbackFactor);
            TEST_PUSHBACK_RESIST_CHANCE(priest, priest, ClassSpells::Priest::FLASH_HEAL_RNK_9, talentResistPushbackFactor);
            TEST_PUSHBACK_RESIST_CHANCE(priest, priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, talentResistPushbackFactor);
            TEST_PUSHBACK_RESIST_CHANCE(priest, priest, ClassSpells::Priest::HEAL_RNK_4, talentResistPushbackFactor);
            TEST_PUSHBACK_RESIST_CHANCE(priest, priest, ClassSpells::Priest::LESSER_HEAL_RNK_3, talentResistPushbackFactor);
            TEST_PUSHBACK_RESIST_CHANCE(priest, priest, ClassSpells::Priest::PRAYER_OF_HEALING_RNK_6, talentResistPushbackFactor);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<HealingFocusTestImpt>();
    }
};

class ImprovedRenewTest : public TestCaseScript
{
public:
    ImprovedRenewTest() : TestCaseScript("talents priest improved_renew") { }

    //"Increases the amount healed by your Renew spell by 15%"
    class ImprovedRenewTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            LearnTalent(priest, Talents::Priest::IMPROVED_RENEW_RNK_3);
            float const talentFactor = 1.15f;

            // Increases by 15% amount healed
            uint32 const renewTicks = 5.0f * floor(ClassSpellsDamage::Priest::RENEW_RNK_12_TICK * talentFactor);
            TEST_DOT_DAMAGE(priest, priest, ClassSpells::Priest::RENEW_RNK_12, renewTicks, true);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedRenewTestImpt>();
    }
};

class HolySpecializationTest : public TestCaseScript
{
public:
    HolySpecializationTest() : TestCaseScript("talents priest holy_specialization") { }

    //"Increases the critical effect chance of your Holy spells by 5%"
    class HolySpecializationTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Creature* dummy = SpawnCreature();

            float const talentCritFactor = 5.f;
            float const expectedSpellCritChance = priest->GetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + SPELL_SCHOOL_SHADOW) + talentCritFactor;

            LearnTalent(priest, Talents::Priest::HOLY_SPECIALIZATION_RNK_5);

            // +5% crit on holy spells
            TEST_SPELL_CRIT_CHANCE(priest, ally, ClassSpells::Priest::BINDING_HEAL_RNK_1, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, priest, ClassSpells::Priest::FLASH_HEAL_RNK_9, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, priest, ClassSpells::Priest::HEAL_RNK_4, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, priest, ClassSpells::Priest::LESSER_HEAL_RNK_3, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, priest, ClassSpells::Priest::PRAYER_OF_HEALING_RNK_6, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, dummy, ClassSpells::Priest::HOLY_FIRE_RNK_9, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, dummy, ClassSpells::Priest::HOLY_NOVA_RNK_7, expectedSpellCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, dummy, ClassSpells::Priest::SMITE_RNK_10, expectedSpellCritChance);
            //Heals from Prayer of Mending will not crit
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<HolySpecializationTestImpt>();
    }
};

class SpellWardingTest : public TestCaseScript
{
public:
    SpellWardingTest() : TestCaseScript("talents priest spell_warding") { }

    //"Reduces all spell damage taken by 10%"
    class SpellWardingTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* enemy = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);

            float const talentFactor = 0.9f;

            LearnTalent(priest, Talents::Priest::SPELL_WARDING_RNK_5);

            // Spell damage taken reduced by 10%
            // Direct
            uint32 const smiteMin = ClassSpellsDamage::Priest::SMITE_RNK_10_MIN * talentFactor;
            uint32 const smiteMax = ClassSpellsDamage::Priest::SMITE_RNK_10_MAX * talentFactor;
            TEST_DIRECT_SPELL_DAMAGE(enemy, priest, ClassSpells::Priest::SMITE_RNK_10, smiteMin, smiteMax, false);
            // DoT
            uint32 const shadowWordPainTotal = 6 * floor(ClassSpellsDamage::Priest::SHADOW_WORD_PAIN_RNK_10_TICK * talentFactor);
            TEST_DOT_DAMAGE(enemy, priest, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, shadowWordPainTotal, true);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<SpellWardingTestImpt>();
    }
};

class DivineFuryTest : public TestCaseScript
{
public:
    DivineFuryTest() : TestCaseScript("talents priest divine_fury") { }

    //"Reduces the casting time of your Smite, Holy Fire, Heal and Greater Heal spells by 0.5 sec"
    class DivineFuryTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            LearnTalent(priest, Talents::Priest::DIVINE_FURY_RNK_5);

            // Reduces cast time for 0.5s
            TEST_CAST_TIME(priest, ClassSpells::Priest::SMITE_RNK_10, 2000);
            TEST_CAST_TIME(priest, ClassSpells::Priest::HOLY_FIRE_RNK_9, 3000);
            TEST_CAST_TIME(priest, ClassSpells::Priest::HEAL_RNK_4, 2500);
            TEST_CAST_TIME(priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, 2500);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<DivineFuryTestImpt>();
    }
};

class HolyNovaTest : public TestCaseScript
{
public:
    HolyNovaTest() : TestCaseScript("talents priest holy_nova") { }

    //Causes an explosion of holy light around the caster, causing 181 to 210 Holy damage to all enemy targets within 10 yards and healing all party members within 10 yards for 302 to 351. These effects cause no threat.
    class HolyNovaTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Creature* dummy = SpawnCreature();

            EQUIP_NEW_ITEM(priest, 34335); // Hammer of Sanctification -- 550 BH
            ally->SetHealth(1);
            ally->DisableRegeneration(true);

            //check no threat + healing done on group
            FORCE_CAST(priest, priest, ClassSpells::Priest::HOLY_NOVA_RNK_1);
            TEST_ASSERT(ally->GetHealth() == 1); // No heal done on ungrouped players
            TEST_ASSERT(dummy->GetThreatManager().GetThreat(priest) == 0.f); // No threat on enemies
            GroupPlayer(priest, ally);
            dummy->EngageWithTarget(ally);
            FORCE_CAST(priest, priest, ClassSpells::Priest::HOLY_NOVA_RNK_1, SPELL_MISS_NONE, TRIGGERED_FULL_MASK);
            // Heal was done on grouped player :
            GetHealingPerSpellsTo(priest, ally, ClassSpells::Priest::HOLY_NOVA_RNK_1_HEAL_LINKED, {}, 1);
            TEST_ASSERT(ally->GetHealth() > 1);
            TEST_ASSERT(dummy->GetThreatManager().GetThreat(priest) == 0.f); // but no threat on healing done either

            int32 const spellLevel = 68;
            float const pointPerLevel = 1.4f;
            float const healPointPerLevel = 1.0f;
            uint32 const pointPerLevelGain = std::max(int32(priest->GetLevel()) - spellLevel, int32(0)) * pointPerLevel;
            uint32 const pointPerLevelHealGain = std::max(int32(priest->GetLevel()) - spellLevel, int32(0)) * healPointPerLevel;

            // Damage
            uint32 spellPower = 183 * ClassSpellsCoeff::Priest::HOLY_NOVA;
            uint32 const holyNovaMinDmg = ClassSpellsDamage::Priest::HOLY_NOVA_RNK_7_MIN + pointPerLevelGain + spellPower;
            uint32 const holyNovaMaxDmg = ClassSpellsDamage::Priest::HOLY_NOVA_RNK_7_MAX + pointPerLevelGain + spellPower;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::HOLY_NOVA_RNK_7, holyNovaMinDmg, holyNovaMaxDmg, false);

            // Heal
            uint32 bonusHeal = 550 * ClassSpellsCoeff::Priest::HOLY_NOVA;
            uint32 const holyNovaMinHeal = ClassSpellsDamage::Priest::HOLY_NOVA_RNK_7_HEAL_MIN + pointPerLevelHealGain + bonusHeal;
            uint32 const holyNovaMaxHeal = ClassSpellsDamage::Priest::HOLY_NOVA_RNK_7_HEAL_MAX + pointPerLevelHealGain + bonusHeal;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::HOLY_NOVA_RNK_7_HEAL_LINKED, holyNovaMinHeal, holyNovaMaxHeal, false);

            // Mana cost
            uint32 const expectedHolyNovaManaCost = 875;
            TEST_POWER_COST(priest, ClassSpells::Priest::HOLY_NOVA_RNK_7, POWER_MANA, expectedHolyNovaManaCost);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<HolyNovaTestImpt>();
    }
};

class BlessedRecoveryTest : public TestCaseScript
{
public:
    BlessedRecoveryTest() : TestCaseScript("talents priest blessed_recovery") { }

    //After being struck by a melee or ranged critical hit, heal 25% of the damage taken over 6sec.
    class BlessedRecoveryTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* shaman = SpawnPlayer(CLASS_SHAMAN, RACE_DRAENEI);

            uint32 const priestStartHealth = 2000;
            priest->SetHealth(priestStartHealth);
            priest->DisableRegeneration(true);

            LearnTalent(priest, Talents::Priest::BLESSED_RECOVERY_RNK_3);
            float const talentRestoreDamageTakenFactor = 0.25f;

            shaman->ForceMeleeHitResult(MELEE_HIT_CRIT);
            shaman->AttackerStateUpdate(priest, BASE_ATTACK);
            shaman->AttackStop();
            TEST_AURA_MAX_DURATION(priest, Talents::Priest::BLESSED_RECOVERY_RNK_3_TRIGGER, Seconds(6));
            shaman->ResetForceMeleeHitResult();

            auto [dealtMin, dealtMax] = GetWhiteDamageDoneTo(shaman, priest, BASE_ATTACK, true, 1);
            float const tickAmount = 3.f;
            uint32 const expectedHeal = dealtMin * talentRestoreDamageTakenFactor / tickAmount;
            uint32 const expectedPriestHealth = priestStartHealth - dealtMin + tickAmount * expectedHeal;
            Wait(6000);
            ASSERT_INFO("Priest has %u HP but %u was expected.", priest->GetHealth(), expectedPriestHealth);
            TEST_ASSERT(priest->GetHealth() == expectedPriestHealth);

            /* WoWWiki: If you receive a critical strike, subsequent critical strikes will NOT stack on top of your existing Blessed Recovery.
            Instead, it will refresh the ability with the value of your last critical strike. For example, if you take a 3000 damage critical hit,
            you will receive the Blessed Recovery effect for 750 damage (assuming max rank). If you take a 1000 damage critical strike directly 
            afterwards, it will overwrite your current Blessed Recovery with a 250 damage restore.*/
            //Keeping previous value is a very specific implemented with SPELL_ATTR0_CU_ROLLING_PERIODIC on our core... this spell does not have it
            //worth testing it?
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<BlessedRecoveryTestImpt>();
    }
};

class InspirationTest : public TestCaseScript
{
public:
    InspirationTest() : TestCaseScript("talents priest inspiration") { }

    //"Increases your target's armor by 25% for 15sec after getting a critical effect from your Flash Heal, Heal, Greater Heal, Binding Heal, Prayer of Healing, or Circle of Healing spell."
    class InspirationTestImpt : public TestCase
    {
    public:


        void AssertInspirationWorksWithSpell(Unit* priest, uint32 spellId, uint32 expectedArmor, Unit* onTarget = nullptr)
        {
            priest->RemoveAurasDueToSpell(Talents::Priest::INSPIRATION_RNK_3_TRIGGER);
            TEST_CAST(priest, onTarget ? onTarget : priest, spellId, SPELL_CAST_OK, TRIGGERED_FULL_MASK);
            TEST_AURA_MAX_DURATION(priest, Talents::Priest::INSPIRATION_RNK_3_TRIGGER, Seconds(15));
            ASSERT_INFO("After spell %u, Priest has %u armor but %u was expected.", spellId, priest->GetArmor(), expectedArmor);
            TEST_ASSERT(priest->GetArmor() == expectedArmor);
        }

        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            LearnTalent(priest, Talents::Priest::INSPIRATION_RNK_3);
            float const talentArmorFactor = 1.25f;

            EnableCriticals(priest, true);

            uint32 const expectedArmor = priest->GetArmor() * talentArmorFactor;

            AssertInspirationWorksWithSpell(priest, ClassSpells::Priest::FLASH_HEAL_RNK_9, expectedArmor);
            AssertInspirationWorksWithSpell(priest, ClassSpells::Priest::HEAL_RNK_4, expectedArmor);
            AssertInspirationWorksWithSpell(priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, expectedArmor);
            AssertInspirationWorksWithSpell(priest, ClassSpells::Priest::BINDING_HEAL_RNK_1, expectedArmor, ally);
            AssertInspirationWorksWithSpell(priest, ClassSpells::Priest::PRAYER_OF_HEALING_RNK_6, expectedArmor);
            AssertInspirationWorksWithSpell(priest, ClassSpells::Priest::CIRCLE_OF_HEALING_RNK_5, expectedArmor);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<InspirationTestImpt>();
    }
};

class HolyReachTest : public TestCaseScript
{
public:
    HolyReachTest() : TestCaseScript("talents priest holy_reach") { }

    //Increases the range of your Smite and Holy Fire spells and the radius of your Prayer of Healing, Holy Nova and Circle of Healing spells by 20%.
    class HolyReachTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Creature* dummy = SpawnCreature();
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            LearnTalent(priest, Talents::Priest::HOLY_REACH_RNK_2);
            float const talentRangeFactor = 1.2f;

            float const expectedSmiteReach = 30.0f * talentRangeFactor;
            float const expectedHolyFireReach = 30.0f * talentRangeFactor;
            float const expectedPrayerOfHealingRadius = 30.0f * talentRangeFactor;
            float const expectedHolyNovagRadius = 10.0f * talentRangeFactor;
            float const expectedCircleOfHealingRadius = 15.0f * talentRangeFactor;

            GroupPlayer(priest, ally);

            TEST_RANGE(priest, dummy, ClassSpells::Priest::SMITE_RNK_10, expectedSmiteReach);
            TEST_RANGE(priest, dummy, ClassSpells::Priest::HOLY_FIRE_RNK_9, expectedSmiteReach);
            //for aoe, check if target was hit
            TEST_RADIUS(priest, priest, ally, ClassSpells::Priest::PRAYER_OF_HEALING_RNK_6, expectedPrayerOfHealingRadius, true);
            TEST_RADIUS(priest, priest, dummy, ClassSpells::Priest::HOLY_NOVA_RNK_7, expectedHolyNovagRadius);
            TEST_RADIUS(priest, priest, ally, ClassSpells::Priest::HOLY_NOVA_RNK_7, expectedHolyNovagRadius, true, ClassSpells::Priest::HOLY_NOVA_RNK_7_HEAL_LINKED);
            TEST_RADIUS(priest, priest, ally, ClassSpells::Priest::CIRCLE_OF_HEALING_RNK_5, expectedCircleOfHealingRadius, true);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<HolyReachTestImpt>();
    }
};

class ImprovedHealingTest : public TestCaseScript
{
public:
    ImprovedHealingTest() : TestCaseScript("talents priest improved_healing") { }

    //"Reduces the mana cost of your Lesser Heal, Heal, and Greater Heal spells by 15%."
    class ImprovedHealingTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            LearnTalent(priest, Talents::Priest::IMPROVED_HEALING_RNK_3);
            float const talentManaCostFactor = 0.85f;

            uint32 expectedLesserHeal = 75 * talentManaCostFactor;
            uint32 expectedHeal = 305 * talentManaCostFactor;
            uint32 expectedGreaterHeal= 825 * talentManaCostFactor;

            TEST_POWER_COST(priest, ClassSpells::Priest::LESSER_HEAL_RNK_3, POWER_MANA, expectedLesserHeal);
            TEST_POWER_COST(priest, ClassSpells::Priest::HEAL_RNK_4, POWER_MANA, expectedHeal);
            TEST_POWER_COST(priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, POWER_MANA, expectedGreaterHeal);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedHealingTestImpt>();
    }
};

class HealingPrayersTest : public TestCaseScript
{
public:
    HealingPrayersTest() : TestCaseScript("talents priest healing_prayers") { }

    //"Reduces the mana cost of your Prayer of Healing and Prayer of Mending spell by 20%."
    class HealingPrayersTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            LearnTalent(priest, Talents::Priest::HEALING_PRAYERS_RNK_2);
            float const talentManaCostFactor = 0.8f;

            uint32 const expectedPrayerOfHealing = 1255 * talentManaCostFactor;
            uint32 const expectedPrayerOfMending = 390 * talentManaCostFactor;

            TEST_POWER_COST(priest, ClassSpells::Priest::PRAYER_OF_HEALING_RNK_6, POWER_MANA, expectedPrayerOfHealing);
            TEST_POWER_COST(priest, ClassSpells::Priest::PRAYER_OF_MENDING_RNK_1, POWER_MANA, expectedPrayerOfMending);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<HealingPrayersTestImpt>();
    }
};

class SpiritOfRedemptionTest : public TestCaseScript
{
public:
    SpiritOfRedemptionTest() : TestCaseScript("talents priest spirit_of_redemption") { }

    //"Increases total Spirit by 5% and upon death, the priest becomes the Spirit of Redemption for 15sec. The Spirit of Redemption cannot move, attack, be attacked or targeted by any spells or effects. While in this form the priest can cast any healing spell free of cost. When the effect ends, the priest dies."
    class SpiritOfRedemptionTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* shaman = SpawnPlayer(CLASS_PRIEST, RACE_DRAENEI);
            Creature* dummy = SpawnCreature();

            float const talentSpiritFactor = 1.05f;
            float const startSpirit = priest->GetStat(STAT_SPIRIT);
            float const expectedSpirit = std::floor(startSpirit * talentSpiritFactor);

            LearnTalent(priest, Talents::Priest::SPIRIT_OF_REDEMPTION_RNK_1);

            // Increases 5% total spirit
            ASSERT_INFO("Priest has %f spirit but %f was expected.", priest->GetStat(STAT_SPIRIT), expectedSpirit);
            TEST_ASSERT(Between<float>(priest->GetStat(STAT_SPIRIT), expectedSpirit - 0.1f, expectedSpirit + 0.1f));

            // PvE
            priest->SetHealth(1);
            dummy->ForceMeleeHitResult(MELEE_HIT_CRIT);
            dummy->AttackerStateUpdate(priest, BASE_ATTACK);
            TEST_AURA_MAX_DURATION(priest, Talents::Priest::SPIRIT_OF_REDEMPTION_RNK_1_TRIGGER, Seconds(15)); // Becomes Spirit of Redemption upon death
            TEST_ASSERT(priest->IsInRoots()); // Priest is rooted, cant move
            TEST_ASSERT(!dummy->IsInCombatWith(priest));
            TEST_ASSERT(!dummy->CanCreatureAttack(priest));
            TEST_ASSERT(!dummy->GetTarget());
            priest->RemoveAurasDueToSpell(Talents::Priest::SPIRIT_OF_REDEMPTION_RNK_1_TRIGGER);
            WaitNextUpdate(); //Wait for JUST_DIED to be handled?
            TEST_ASSERT(priest->IsDead()); // Priest is dead without the aura
            priest->ResurrectPlayer(0.01f);
            dummy->DespawnOrUnsummon();

            // PvP
            TEST_CAST(priest, priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, SPELL_CAST_OK, TRIGGERED_IGNORE_POWER_AND_REAGENT_COST);
            Wait(Seconds(1));
            shaman->SetSelection(priest->GetGUID());
            FORCE_CAST(shaman, priest, ClassSpells::Shaman::EARTH_SHOCK_RNK_8);
            TEST_HAS_AURA(priest, Talents::Priest::SPIRIT_OF_REDEMPTION_RNK_1_TRIGGER);
            // Full health & mana entering SoR: https://youtu.be/cWex4bleNzE?t=7m23s
            TEST_ASSERT(priest->IsFullHealth());
            TEST_ASSERT(priest->GetPower(POWER_MANA) == priest->GetMaxPower(POWER_MANA));
            // Cooldown/Silences/Interrupted effects are not removed by death (https://web.archive.org/web/20071214220852/http://forums.worldofwarcraft.com/thread.html?topicId=108205229&sid=1)
            TEST_CAST(priest, ally, ClassSpells::Priest::GREATER_HEAL_RNK_7, SPELL_FAILED_NOT_READY);
            // Power cost
            TEST_POWER_COST(priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, POWER_MANA, uint32(0));
            TEST_POWER_COST(priest, ClassSpells::Priest::FLASH_HEAL_RNK_9, POWER_MANA, uint32(0));
            TEST_POWER_COST(priest, ClassSpells::Priest::BINDING_HEAL_RNK_1, POWER_MANA, uint32(0));
            TEST_POWER_COST(priest, ClassSpells::Priest::HEAL_RNK_4, POWER_MANA, uint32(0));
            TEST_POWER_COST(priest, ClassSpells::Priest::LESSER_HEAL_RNK_3, POWER_MANA, uint32(0));
            TEST_POWER_COST(priest, ClassSpells::Priest::PRAYER_OF_HEALING_RNK_6, POWER_MANA, uint32(0));
            TEST_POWER_COST(priest, ClassSpells::Priest::CIRCLE_OF_HEALING_RNK_5, POWER_MANA, uint32(0));
            TEST_POWER_COST(priest, ClassSpells::Priest::PRAYER_OF_MENDING_RNK_1, POWER_MANA, uint32(0));
            TEST_POWER_COST(priest, ClassSpells::Priest::RENEW_RNK_12, POWER_MANA, uint32(0));

            // Cannot cast other spells
            TEST_CAST(priest, priest, ClassSpells::Priest::PSYCHIC_SCREAM_RNK_4, SPELL_FAILED_NOT_SHAPESHIFT);

            // Attacker should lose target (https://youtu.be/kpz_t8kkbnA?t=49s)
            //Commented out: just a quirck and not really a gameplay feature. //TEST_ASSERT(!shaman->GetSelectedPlayer());
            
            // SoR cannot be targeted by spells
            TEST_CAST(shaman, priest, ClassSpells::Shaman::EARTH_SHOCK_RNK_8, SPELL_FAILED_BAD_TARGETS, TRIGGERED_FULL_MASK);
            //don't really care if SoR can be targeted by heal

            // SoR cannot be melee attacked
            TEST_ASSERT(shaman->IsValidAttackTarget(priest) == false); //(this is what is check when players send attack swing opcode)
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<SpiritOfRedemptionTestImpt>();
    }
};

class SpiritualGuidanceTest : public TestCaseScript
{
public:
    SpiritualGuidanceTest() : TestCaseScript("talents priest spiritual_guidance") { }

    //"Increases spell damage and healing by up to 25 % of your total Spirit."
    class SpiritualGuidanceTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            float const spirit = priest->GetStat(STAT_SPIRIT);

            EQUIP_NEW_ITEM(priest, 34335); // Hammer of Sanctification -- 183 SP & 550 BH
            uint32 const bonusHeal = priest->GetInt32Value(PLAYER_FIELD_MOD_HEALING_DONE_POS);
            TEST_ASSERT(bonusHeal == 550);
            uint32 const spellPower = priest->GetInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + SPELL_SCHOOL_SHADOW);
            TEST_ASSERT(spellPower == 183);

            float const talentFactor = 0.25f;
            uint32 expectedBH = bonusHeal + spirit * talentFactor;
            uint32 expectedSP = spellPower + spirit * talentFactor;

            LearnTalent(priest, Talents::Priest::SPIRITUAL_GUIDANCE_RNK_5);
            TEST_ASSERT(priest->GetInt32Value(PLAYER_FIELD_MOD_HEALING_DONE_POS) == int32(expectedBH));
            TEST_ASSERT(priest->GetInt32Value(PLAYER_FIELD_MOD_DAMAGE_DONE_POS + SPELL_SCHOOL_SHADOW) == int32(expectedSP));
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<SpiritualGuidanceTestImpt>();
    }
};

class SurgeOfLightTest : public TestCaseScript
{
public:
    SurgeOfLightTest() : TestCaseScript("talents priest surge_of_light") { }

    //"Your spell criticals have a 50% chance to cause your next Smite spell to be instant cast, cost no mana but be incapable of a critical hit. This effect lasts 10sec."
    class SurgeOfLightTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* enemy = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);

            LearnTalent(priest, Talents::Priest::SURGE_OF_LIGHT_RNK_2);
            uint32 const spellProcId = Talents::Priest::SURGE_OF_LIGHT_RNK_2_TRIGGER;
            float const procChance = 50.f;

            // Smite to be instant cast, no mana, no crit
            priest->AddAura(spellProcId, priest);
            TEST_AURA_MAX_DURATION(priest, spellProcId, Seconds(10));
            TEST_CAST_TIME(priest, ClassSpells::Priest::SMITE_RNK_10, uint32(0));
            TEST_POWER_COST(priest, ClassSpells::Priest::SMITE_RNK_10, POWER_MANA, uint32(0));
            uint32 const minSmite = ClassSpellsDamage::Priest::SMITE_RNK_10_MIN;
            uint32 const maxSmite = ClassSpellsDamage::Priest::SMITE_RNK_10_MAX;
            TEST_DIRECT_SPELL_DAMAGE(priest, enemy, ClassSpells::Priest::SMITE_RNK_10, minSmite, maxSmite, false, [spellProcId](Unit* caster, Unit* victim) {
                //priest will have 100% crit chance in the test. Since we're testing with non crit damages this will ensure no crit was done.
                for (int i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; i++)
                    caster->SetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + i, 200.f);
                caster->AddAura(spellProcId, caster);
            });

            float twoTargetsProcChance = (1.0f - std::pow(1.0f - procChance / 100.0f, 2)) * 100.0f; //spell hits two targets... two times the proc chances!
            // Damage
            TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::SMITE_RNK_10, spellProcId, true, procChance, SPELL_MISS_NONE, true);
            TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::HOLY_FIRE_RNK_9, spellProcId, true, procChance, SPELL_MISS_NONE, true);
            TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::MIND_BLAST_RNK_11, spellProcId, true, procChance, SPELL_MISS_NONE, true);
            TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2, spellProcId, true, procChance, SPELL_MISS_NONE, true);
            TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::HOLY_NOVA_RNK_7, spellProcId, true, twoTargetsProcChance, SPELL_MISS_NONE, true);

            // Heal
            enemy->KillSelf();
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            GroupPlayer(priest, ally);
            TEST_SPELL_PROC_CHANCE(priest, ally, ClassSpells::Priest::BINDING_HEAL_RNK_1, spellProcId, true, twoTargetsProcChance, SPELL_MISS_NONE, true);
            TEST_SPELL_PROC_CHANCE(priest, ally, ClassSpells::Priest::CIRCLE_OF_HEALING_RNK_5, spellProcId, true, twoTargetsProcChance, SPELL_MISS_NONE, true);
            TEST_SPELL_PROC_CHANCE(priest, ally, ClassSpells::Priest::FLASH_HEAL_RNK_9, spellProcId, true, procChance, SPELL_MISS_NONE, true);
            TEST_SPELL_PROC_CHANCE(priest, ally, ClassSpells::Priest::GREATER_HEAL_RNK_7, spellProcId, true, procChance, SPELL_MISS_NONE, true);
            TEST_SPELL_PROC_CHANCE(priest, ally, ClassSpells::Priest::HEAL_RNK_4, spellProcId, true, procChance, SPELL_MISS_NONE, true);
            TEST_SPELL_PROC_CHANCE(priest, ally, ClassSpells::Priest::PRAYER_OF_HEALING_RNK_6, spellProcId, true, twoTargetsProcChance, SPELL_MISS_NONE, true);
            TEST_SPELL_PROC_CHANCE(priest, ally, ClassSpells::Priest::HOLY_NOVA_RNK_7_HEAL_LINKED, spellProcId, true, twoTargetsProcChance, SPELL_MISS_NONE, true);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<SurgeOfLightTestImpt>();
    }
};

class SpiritualHealingTest : public TestCaseScript
{
public:
    SpiritualHealingTest() : TestCaseScript("talents priest spiritual_healing") { }

    //"Increases the amount healed by your healing spells by 10%"
    class SpiritualHealingTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* enemy = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);

            GroupPlayer(priest, ally);

            LearnTalent(priest, Talents::Priest::SPIRITUAL_HEALING_RNK_5);
            float const talentFactor = 1.1f;

            // Binding Heal
            uint32 const bindingHealMin = ClassSpellsDamage::Priest::BINDING_HEAL_RNK_1_MIN * talentFactor;
            uint32 const bindingHealMax = ClassSpellsDamage::Priest::BINDING_HEAL_RNK_1_MAX * talentFactor;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::BINDING_HEAL_RNK_1, bindingHealMin, bindingHealMax, false);

            // Circle of Healing
            uint32 const cohMin = ClassSpellsDamage::Priest::CIRCLE_OF_HEALING_RNK_5_MIN * talentFactor;
            uint32 const cohMax = ClassSpellsDamage::Priest::CIRCLE_OF_HEALING_RNK_5_MAX * talentFactor;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::CIRCLE_OF_HEALING_RNK_5, cohMin, cohMax, false);

            // Flash Heal
            uint32 const flashHealMin = ClassSpellsDamage::Priest::FLASH_HEAL_RNK_9_MIN * talentFactor;
            uint32 const flashHealMax = ClassSpellsDamage::Priest::FLASH_HEAL_RNK_9_MAX * talentFactor;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::FLASH_HEAL_RNK_9, flashHealMin, flashHealMax, false);

            // Greater Heal
            uint32 const greaterHealMin = ClassSpellsDamage::Priest::GREATER_HEAL_RNK_7_MIN * talentFactor;
            uint32 const greaterHealMax = ClassSpellsDamage::Priest::GREATER_HEAL_RNK_7_MAX * talentFactor;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::GREATER_HEAL_RNK_7, greaterHealMin, greaterHealMax, false);

            // Heal
            uint32 const healMin = ClassSpellsDamage::Priest::HEAL_RNK_4_MIN_LVL_70 * talentFactor;
            uint32 const healMax = ClassSpellsDamage::Priest::HEAL_RNK_4_MAX_LVL_70 * talentFactor;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::HEAL_RNK_4, healMin, healMax, false);

            // Holy Nova
            uint32 const holyNovaMin = ClassSpellsDamage::Priest::HOLY_NOVA_RNK_7_HEAL_MIN_LVL_70 * talentFactor;
            uint32 const holyNovaMax = ClassSpellsDamage::Priest::HOLY_NOVA_RNK_7_HEAL_MAX_LVL_70 * talentFactor;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::HOLY_NOVA_RNK_7_HEAL_LINKED, holyNovaMin, holyNovaMax, false);

            // Lesser Heal
            uint32 const lesserHealMin = ClassSpellsDamage::Priest::LESSER_HEAL_RNK_3_MIN_LVL_70 * talentFactor;
            uint32 const lesserHealMax = ClassSpellsDamage::Priest::LESSER_HEAL_RNK_3_MAX_LVL_70 * talentFactor;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::LESSER_HEAL_RNK_3, lesserHealMin, lesserHealMax, false);

            // Lightwell Renew
            uint32 const lightwellTotal = 3 * floor(ClassSpellsDamage::Priest::LIGHTWELL_RNK_4_TICK * talentFactor);
            TEST_DOT_DAMAGE(priest, ally, ClassSpells::Priest::LIGHTWELL_RNK_4_TRIGGER, lightwellTotal, false);

            // Prayer of Healing
            uint32 const pohMin = ClassSpellsDamage::Priest::PRAYER_OF_HEALING_RNK_6_MIN * talentFactor;
            uint32 const pohMax = ClassSpellsDamage::Priest::PRAYER_OF_HEALING_RNK_6_MAX * talentFactor;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::PRAYER_OF_HEALING_RNK_6, pohMin, pohMax, false);

            // Renew
            uint32 const renewTotal = 5 * floor(ClassSpellsDamage::Priest::RENEW_RNK_12_TICK * talentFactor);
            TEST_DOT_DAMAGE(priest, ally, ClassSpells::Priest::RENEW_RNK_12, renewTotal, false);

            // Prayer of Mending
            uint32 const pomHeal = ClassSpellsDamage::Priest::PRAYER_OF_MENDING_RNK_1 * talentFactor;
            ally->DisableRegeneration(true);
            ally->SetHealth(500);
            TEST_CAST(priest, ally, ClassSpells::Priest::PRAYER_OF_MENDING_RNK_1);
            enemy->ForceMeleeHitResult(MELEE_HIT_NORMAL);
            enemy->AttackerStateUpdate(ally);
            WaitNextUpdate();
            auto [minDmg, maxDmg] = GetWhiteDamageDoneTo(enemy, ally, BASE_ATTACK, false, 1);
            uint32 const allyExpectedHealth = 500 - minDmg + pomHeal;
            ASSERT_INFO("Ally has %u HP but %u HP was expected.", ally->GetHealth(), allyExpectedHealth);
            TEST_ASSERT(ally->GetHealth() == allyExpectedHealth);

        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<SpiritualHealingTestImpt>();
    }
};

class HolyConcentrationTest : public TestCaseScript
{
public:
    HolyConcentrationTest() : TestCaseScript("talents priest holy_concentration") { }

    //"Gives you a 6% chance to enter a Clearcasting state after casting any Flash Heal, Binding Heal, or Greater Heal spell. The Clearcasting state reduces the mana cost of your next Flash Heal, Binding Heal, or Greater Heal spell by 100%."
    class HolyConcentrationTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            GroupPlayer(priest, ally);

            LearnTalent(priest, Talents::Priest::HOLY_CONCENTRATION_RNK_3);
            uint32 const spellProcId = Talents::Priest::HOLY_CONCENTRATION_RNK_3_TRIGGER;
            float const procChance = 6.f;

            // No mana cost
            priest->AddAura(spellProcId, priest);
            TEST_AURA_MAX_DURATION(priest, spellProcId, Seconds(15));
            TEST_POWER_COST(priest, ClassSpells::Priest::FLASH_HEAL_RNK_9, POWER_MANA, uint32(0));
            TEST_POWER_COST(priest, ClassSpells::Priest::BINDING_HEAL_RNK_1, POWER_MANA, uint32(0));
            TEST_POWER_COST(priest, ClassSpells::Priest::GREATER_HEAL_RNK_7, POWER_MANA, uint32(0));

            // Proc chance
            float twoTargetsProcChance = (1.0f - std::pow(1.0f - procChance / 100.0f, 2)) * 100.0f; //1 - (hit nobody chance) // spell hits two targets... two times the proc chances!
            TEST_SPELL_PROC_CHANCE(priest, ally, ClassSpells::Priest::FLASH_HEAL_RNK_9, spellProcId, true, procChance, SPELL_MISS_NONE, false);
            // Binding Heal can proc the talent on each target meaning more procs: bug or not? -> Good question. Consider it's correct for now
            TEST_SPELL_PROC_CHANCE(priest, ally, ClassSpells::Priest::BINDING_HEAL_RNK_1, spellProcId, true, twoTargetsProcChance, SPELL_MISS_NONE, false);
            TEST_SPELL_PROC_CHANCE(priest, ally, ClassSpells::Priest::GREATER_HEAL_RNK_7, spellProcId, true, procChance, SPELL_MISS_NONE, false);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<HolyConcentrationTestImpt>();
    }
};

class BlessedResilienceTest : public TestCaseScript
{
public:
    BlessedResilienceTest() : TestCaseScript("talents priest blessed_resilience") { }

    //"Critical hits made against you have a 60% chance to prevent you from being critically hit again for 6sec."
    class BlessedResilienceTestImpt : public TestCase
    {
    public:


        //test if priest can take white melee crit
        void TestMeleeCrit(Unit* priest, Unit* enemy)
        {
            priest->AddAura(Talents::Priest::BLESSED_RESILIENCE_RNK_3_TRIGGER, priest);
            priest->SetFloatValue(PLAYER_DODGE_PERCENTAGE, 0.0f);
            enemy->m_modMeleeHitChance = 10000.0f; //LOTS OF HIT SCORE
            EnableCriticals(enemy, true);
            uint32 const sampleSize = 10;
            for(uint8 i = 0; i < 10; i++)
                enemy->AttackerStateUpdate(priest, BASE_ATTACK);

            //if failure here: it means we got a dodge, miss or something like that... GetWhiteDamageDoneTo expect only hit and crits
            auto[dealtMin, dealtMax] = GetWhiteDamageDoneTo(enemy, priest, BASE_ATTACK, false, sampleSize);
            //no need to check damage, if we get here, sampleSize non-crit hits were found by GetWhiteDamageDoneTo
            //if sample size is wrong here, it might also be because other non normal hits were found (such as dodge)
        }

        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* enemy = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);

            LearnTalent(priest, Talents::Priest::BLESSED_RESILIENCE_RNK_3);
            uint32 const spellProcId = Talents::Priest::BLESSED_RESILIENCE_RNK_3_TRIGGER;
            float const procChance = 60.f;

            // Cant be critically hit
            priest->AddAura(spellProcId, priest);
            TEST_AURA_MAX_DURATION(priest, spellProcId, Seconds(6));
            // By spells
            uint32 const minSmite = ClassSpellsDamage::Priest::SMITE_RNK_10_MIN;
            uint32 const maxSmite = ClassSpellsDamage::Priest::SMITE_RNK_10_MAX;
            //make sure the enemy with 100% critical won't get any crit on our priest
            TEST_DIRECT_SPELL_DAMAGE(enemy, priest, ClassSpells::Priest::SMITE_RNK_10, minSmite, maxSmite, false, [spellProcId](Unit* caster, Unit* victim) {
                for (int i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; i++)
                    caster->SetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + i, 200.f);
                caster->AddAura(spellProcId, caster);
            });
            TestMeleeCrit(priest, enemy);

            //Proc chance
           // TEST_MELEE_PROC_CHANCE(enemy, priest, spellProcId, false, procChance, MELEE_HIT_CRIT, BASE_ATTACK);
           // TEST_SPELL_PROC_CHANCE(enemy, priest, ClassSpells::Priest::SMITE_RNK_10, spellProcId, false, procChance, SPELL_MISS_NONE, true);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<BlessedResilienceTestImpt>();
    }
};

class EmpoweredHealingTest : public TestCaseScript
{
public:
    EmpoweredHealingTest() : TestCaseScript("talents priest empowered_healing") { }

    //Your Greater Heal spell gains an additional 20% and your Flash Heal and Binding Heal gain an additional 10% of your bonus healing effects.
    class EmpoweredHealingTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            GroupPlayer(priest, ally);
            EQUIP_NEW_ITEM(priest, 34335); // Hammer of Sanctification -- 550 BH
            uint32 const bonusHeal = 550;

            LearnTalent(priest, Talents::Priest::EMPOWERED_HEALING_RNK_5);
            float const talentGreaterHealFactor = 0.2f;
            float const talentFlashHealFactor = 0.1f;
            float const talentBindingHealFactor = 0.1;

            // Binding Heal
            uint32 const bhBonus = (ClassSpellsCoeff::Priest::BINDING_HEAL + talentBindingHealFactor) * bonusHeal;
            uint32 const bindingHealMin = ClassSpellsDamage::Priest::BINDING_HEAL_RNK_1_MIN + bhBonus;
            uint32 const bindingHealMax = ClassSpellsDamage::Priest::BINDING_HEAL_RNK_1_MAX + bhBonus;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::BINDING_HEAL_RNK_1, bindingHealMin, bindingHealMax, false);

            // Flash Heal
            uint32 const fhBonus = (ClassSpellsCoeff::Priest::FLASH_HEAL + talentFlashHealFactor) * bonusHeal;
            uint32 const flashHealMin = ClassSpellsDamage::Priest::FLASH_HEAL_RNK_9_MIN + fhBonus;
            uint32 const flashHealMax = ClassSpellsDamage::Priest::FLASH_HEAL_RNK_9_MAX + fhBonus;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::FLASH_HEAL_RNK_9, flashHealMin, flashHealMax, false);

            // Greater Heal
            uint32 const ghBonus = (ClassSpellsCoeff::Priest::GREATER_HEAL + talentGreaterHealFactor) * bonusHeal;
            uint32 const greaterHealMin = ClassSpellsDamage::Priest::GREATER_HEAL_RNK_7_MIN + ghBonus;
            uint32 const greaterHealMax = ClassSpellsDamage::Priest::GREATER_HEAL_RNK_7_MAX + ghBonus;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::GREATER_HEAL_RNK_7, greaterHealMin, greaterHealMax, false);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<EmpoweredHealingTestImpt>();
    }
};

class CircleOfHealingTest : public TestCaseScript
{
public:
    CircleOfHealingTest() : TestCaseScript("talents priest circle_of_healing") { }

    //Heals friendly target and that target's party members within 15 yards of the target for 409 to 452.
    class CircleOfHealingTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            Position spawn;
            spawn.MoveInFront(_location, 40.f);
            TestPlayer* ally = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF, 70, spawn);
            spawn.MoveInFront(_location, 55.f);
            TestPlayer* allyFarAway = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF, 70, spawn);

            ally->SetHealth(1);
            allyFarAway->SetHealth(1);
            ally->DisableRegeneration(true);
            allyFarAway->DisableRegeneration(true);
            GroupPlayer(priest, ally);
            GroupPlayer(priest, allyFarAway);
            EQUIP_NEW_ITEM(priest, 34335); // Hammer of Sanctification -- 550 BH
            uint32 const bonusHeal = 550;

            // Ranges: heals 40m away ally and all allies within 15m
            TEST_CAST(priest, ally, ClassSpells::Priest::CIRCLE_OF_HEALING_RNK_5);
            TEST_ASSERT(ally->GetHealth() > 1);
            TEST_ASSERT(allyFarAway->GetHealth() > 1);

            // Heal
            uint32 const cohBonusHeal = ClassSpellsCoeff::Priest::CIRCLE_OF_HEALING * bonusHeal;
            uint32 const cohHealMin = ClassSpellsDamage::Priest::CIRCLE_OF_HEALING_RNK_5_MIN + cohBonusHeal;
            uint32 const cohHealMax = ClassSpellsDamage::Priest::CIRCLE_OF_HEALING_RNK_5_MAX + cohBonusHeal;
            TEST_DIRECT_HEAL(priest, ally, ClassSpells::Priest::CIRCLE_OF_HEALING_RNK_5, cohHealMin, cohHealMax, false);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<CircleOfHealingTestImpt>();
    }
};

class SpiritTapTest : public TestCaseScript
{
public:
    SpiritTapTest() : TestCaseScript("talents priest spirit_tap") { }

    /*"Gives you a 100 % chance to gain a 100 % bonus to your Spirit after killing a target that yields experience or honor.
    For the duration, your mana will regenerate at a 50 % rate while casting. Lasts 15sec."*/
    class SpiritTapTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            _location.MoveInFront(_location, 10.0f);
            TestPlayer* enemy = SpawnPlayer(CLASS_PRIEST, RACE_TROLL);
            enemy->DisableRegeneration(true);

            LearnTalent(priest, Talents::Priest::SPIRIT_TAP_RNK_5);
            float const expectedSpirit = priest->GetStat(STAT_SPIRIT) * 2.f;

            // Kill enemy
            enemy->SetHealth(1);
            FORCE_CAST(priest, enemy, ClassSpells::Priest::SMITE_RNK_10, SPELL_MISS_NONE, TRIGGERED_FULL_MASK);
            WaitNextUpdate();
            TEST_ASSERT(enemy->IsDead());
            TEST_AURA_MAX_DURATION(priest, Talents::Priest::SPIRIT_TAP_RNK_5_TRIGGER, Seconds(15));
            // Spirit boost
            TEST_ASSERT(Between<float>(priest->GetStat(STAT_SPIRIT), expectedSpirit - 0.1f, expectedSpirit + 0.1f));
            // Regen boost
            float const expectedManaRegen = priest->GetFloatValue(PLAYER_FIELD_MOD_MANA_REGEN) * 0.5f;
            TEST_ASSERT(Between<float>(priest->GetFloatValue(PLAYER_FIELD_MOD_MANA_REGEN_INTERRUPT), expectedManaRegen - 0.1f, expectedManaRegen + 0.1f));

            // Doesnt trigger when no killing blow. Have the priest tag the enemy but the enemy dies by another cause (swd in this case)
            priest->RemoveAurasDueToSpell(Talents::Priest::SPIRIT_TAP_RNK_5_TRIGGER);
            enemy->ResurrectPlayer(0.01f);
            FORCE_CAST(priest, enemy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_1);
            Wait(4000); // Wait for 1 tick
            TEST_ASSERT(!enemy->IsDead());
            FORCE_CAST(enemy, priest, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_1, SPELL_MISS_NONE, TRIGGERED_IGNORE_POWER_AND_REAGENT_COST);
            Wait(1000);
            TEST_ASSERT(enemy->IsDead());
            TEST_HAS_NOT_AURA(priest, Talents::Priest::SPIRIT_TAP_RNK_5_TRIGGER);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<SpiritTapTestImpt>();
    }
};

class BlackoutTest : public TestCaseScript
{
public:
    BlackoutTest() : TestCaseScript("talents priest blackout") { }

    //Gives your Shadow damage spells a $h% chance to stun the target for $15269d.
    //WoWWiki: It does not trigger off spell ticks (such as each tick of Shadow Word: Pain), only at the initial hit of the spell.
    class BlackoutTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            TestPlayer* enemy = SpawnPlayer(CLASS_PRIEST, RACE_TROLL);

            LearnTalent(priest, Talents::Priest::BLACKOUT_RNK_5);
            uint32 const spellProcId = Talents::Priest::BLACKOUT_RNK_5_TRIGGER;
            float const procChance = 10.f;

            // Only affects shadow damage spells
            SECTION("Proc", [&] {
                TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::MIND_BLAST_RNK_11, spellProcId, false, procChance, SPELL_MISS_NONE, false);
                TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2, spellProcId, false, procChance, SPELL_MISS_NONE, false);
                TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, spellProcId, false, procChance, SPELL_MISS_NONE, false);
                TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, spellProcId, false, procChance, SPELL_MISS_NONE, false);
                TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::HEX_OF_WEAKNESS_RNK_7, spellProcId, false, procChance, SPELL_MISS_NONE, false);
            }); 
            
            SECTION("Mind Flay", [&] {
                TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::MIND_FLAY_RNK_7, spellProcId, false, procChance, SPELL_MISS_NONE, false);
            });
            SECTION("Touch of weakness", [&] {
                //WoWWiki: Since Shadow damage is dealt to the target, Touch of Weakness will apply a charge of Shadow Weaving to the target, and it also has a chance to proc Blackout, making it a useful tool against Rogues, Warriors, Enhancement Shamans, etc.
                priest->ForceSpellHitResultOverride(SPELL_MISS_NONE);//force non miss to test touch of weakness proc
                TEST_MELEE_PROC_CHANCE(enemy, priest, spellProcId, true, procChance, MELEE_HIT_NORMAL, BASE_ATTACK, [](Unit* caster, Unit* victim) {
                    victim->AddAura(ClassSpells::Priest::TOUCH_OF_WEAKNESS_RNK_7, victim);
                });
            });
            SECTION("Devouring plague", [&] {
                TEST_SPELL_PROC_CHANCE(priest, enemy, ClassSpells::Priest::DEVOURING_PLAGUE_RNK_7, spellProcId, false, procChance, SPELL_MISS_NONE, false);
            });
            SECTION("Shadowguard", STATUS_WIP, [&] {
                priest->ForceSpellHitResultOverride(SPELL_MISS_NONE);//force non miss to test shadowguard proc
                TEST_MELEE_PROC_CHANCE(enemy, priest, spellProcId, true, procChance, MELEE_HIT_NORMAL, BASE_ATTACK, [](Unit* caster, Unit* victim) {
                    victim->AddAura(ClassSpells::Priest::SHADOW_GUARD_RNK_7, victim);
                });
            });
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<BlackoutTestImpt>();
    }
};

class ShadowAffinityTest : public TestCaseScript
{
public:
    ShadowAffinityTest() : TestCaseScript("talents priest shadow_affinity") { }

    //"Reduces the threat generated by your Shadow spells by 25%"
    class ShadowAffinityTestImpt : public TestCase
    {
    public:


        void TestMeleeProcThreat(Unit* dummy, TestPlayer* priest, float talentThreatFactor, uint32 spellId, uint32 expectedDmg)
        {
            priest->ForceSpellHitResultOverride(SPELL_MISS_NONE);
            float startThreat = dummy->GetThreatManager().GetThreat(priest);
            float const expectedToWThreat = startThreat + expectedDmg * talentThreatFactor;
            TEST_CAST(priest, priest, spellId, SPELL_CAST_OK, TRIGGERED_FULL_MASK);
            dummy->ForceMeleeHitResult(MELEE_HIT_NORMAL);
            uint32 targetStartHealth = dummy->GetHealth();
            dummy->AttackerStateUpdate(priest, BASE_ATTACK);
            dummy->AttackStop();
            TEST_ASSERT(dummy->GetHealth() < targetStartHealth); //make sure proc did happen
            ASSERT_INFO("%s, dummy has %f threat but %f was expected.", _SpellString(spellId).c_str(), dummy->GetThreatManager().GetThreat(priest), expectedToWThreat);
            TEST_ASSERT(Between<float>(dummy->GetThreatManager().GetThreat(priest), expectedToWThreat - 0.1f, expectedToWThreat + 0.1f));
        }

        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            Creature* dummy = SpawnCreature();

            LearnTalent(priest, Talents::Priest::SHADOW_AFFINITY_RNK_3);
            float const talentThreatFactor = 1 - 0.25f;

            TEST_THREAT(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, talentThreatFactor);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2, talentThreatFactor);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, talentThreatFactor);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::MIND_FLAY_RNK_7, talentThreatFactor);
            TEST_THREAT(priest, dummy, ClassSpells::Priest::DEVOURING_PLAGUE_RNK_7, talentThreatFactor);

            // Vampiric Touch
            float startThreat = dummy->GetThreatManager().GetThreat(priest);
            float const vtManaRestoreThreat = 5.f * floor(ClassSpellsDamage::Priest::VAMPIRIC_TOUCH_RNK_3_TICK * 0.05f) * 0.5f; //  Power Gain have a 0.5f threat factor
            float const expectedVTThreat = startThreat + ClassSpellsDamage::Priest::VAMPIRIC_TOUCH_RNK_3_TOTAL * talentThreatFactor + vtManaRestoreThreat;
            FORCE_CAST(priest, dummy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, SPELL_MISS_NONE, TRIGGERED_CAST_DIRECTLY);
            Wait(15000);
            ASSERT_INFO("Dummy has %f threat but %f was expected.", dummy->GetThreatManager().GetThreat(priest), expectedVTThreat);
            TEST_ASSERT(Between<float>(dummy->GetThreatManager().GetThreat(priest), expectedVTThreat - 0.1f, expectedVTThreat + 0.1f));

            // Vampiric Embrace
            // WoWWiki: "It does affect Vampiric Embrace's healing. If you plan to raid as a Shadow priest, this is a worthwhile talent."
            //note: this is handled with a custom family flag added to 15290 spell
            priest->SetHealth(500);
            startThreat = dummy->GetThreatManager().GetThreat(priest);
            float const mindFlayThreat = 3.f * ClassSpellsDamage::Priest::MIND_FLAY_RNK_7_TICK;
            float const mindFlayHealThreat = 3.f * floor(ClassSpellsDamage::Priest::MIND_FLAY_RNK_7_TICK * 0.15f) * 0.5f;
            float const expectedVEThreat = startThreat + (mindFlayThreat + mindFlayHealThreat) * talentThreatFactor;
            FORCE_CAST(priest, dummy, ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1);
            FORCE_CAST(priest, dummy, ClassSpells::Priest::MIND_FLAY_RNK_7, SPELL_MISS_NONE, TRIGGERED_IGNORE_GCD);
            _StartUnitChannels(priest);
            Wait(3000);
            ASSERT_INFO("Dummy has %f threat but %f was expected.", dummy->GetThreatManager().GetThreat(priest), expectedVEThreat);
            TEST_ASSERT(Between<float>(dummy->GetThreatManager().GetThreat(priest), expectedVEThreat - 0.1f, expectedVEThreat + 0.1f));
            dummy->RemoveAurasDueToSpell(ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1);

            // Touch of Weakness
            TestMeleeProcThreat(dummy, priest, talentThreatFactor, ClassSpells::Priest::TOUCH_OF_WEAKNESS_RNK_7, ClassSpellsDamage::Priest::TOUCH_OF_WEAKNESS_RNK_7);
            TestMeleeProcThreat(dummy, priest, talentThreatFactor, ClassSpells::Priest::SHADOW_GUARD_RNK_7, ClassSpellsDamage::Priest::SHADOW_GUARD_RNK_7);
        }   
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ShadowAffinityTestImpt>();
    }
};

class ImprovedShadowWordPainTest : public TestCaseScript
{
public:
    ImprovedShadowWordPainTest() : TestCaseScript("talents priest improved_shadow_word_pain") { }

    //Increases the duration of your Shadow Word : Pain spell by 6 sec.
    class ImprovedShadowWordPainTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            Creature* dummy = SpawnCreature();

            LearnTalent(priest, Talents::Priest::IMPROVED_SHADOW_WORD_PAIN_RNK_2);

            uint32 const swpTotal = 8 * ClassSpellsDamage::Priest::SHADOW_WORD_PAIN_RNK_10_TICK;
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, swpTotal, false);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedShadowWordPainTestImpt>();
    }
};

class ShadowFocusTest : public TestCaseScript
{
public:
    ShadowFocusTest() : TestCaseScript("talents priest shadow_focus") { }

    //"Reduces your target's chance to resist your Shadow spells by 10%."
    class ShadowFocusTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            Creature* boss = SpawnBoss();

            LearnTalent(priest, Talents::Priest::SHADOW_FOCUS_RNK_5);
            float const talentHitFactor = 10.f;
            float const hitChance = 16.f - talentHitFactor;

            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::HEX_OF_WEAKNESS_RNK_7, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::MIND_BLAST_RNK_11, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::MIND_FLAY_RNK_7, hitChance, SPELL_MISS_RESIST);

            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::MIND_SOOTHE_RNK_4, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::MIND_VISION_RNK_2, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::PSYCHIC_SCREAM_RNK_4, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::SILENCE_RNK_1, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::DEVOURING_PLAGUE_RNK_7, hitChance, SPELL_MISS_RESIST);
            //touch of weakness and shadow guard, directly test the proc spell (this makes absolutely no difference, proc chance is not tested here)
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::TOUCH_OF_WEAKNESS_RNK_7_TRIGGER_AURA, hitChance, SPELL_MISS_RESIST);
            TEST_SPELL_HIT_CHANCE(priest, boss, ClassSpells::Priest::SHADOW_GUARD_RNK_7_TRIGGER, hitChance, SPELL_MISS_RESIST);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ShadowFocusTestImpt>();
    }
};

class ImprovedPsychicScreamTest : public TestCaseScript
{
public:
    ImprovedPsychicScreamTest() : TestCaseScript("talents priest improved_psychic_scream") { }

    //"Reduces the cooldown of your Psychic Scream spell by 4 sec."
    class ImprovedPsychicScreamTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);

            LearnTalent(priest, Talents::Priest::IMPROVED_PSYCHIC_SCREAM_RNK_2);

            TEST_COOLDOWN(priest, priest, ClassSpells::Priest::PSYCHIC_SCREAM_RNK_4, Seconds(26)); //down from 30
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedPsychicScreamTestImpt>();
    }
};

class ImprovedMindBlastTest : public TestCaseScript
{
public:
    ImprovedMindBlastTest() : TestCaseScript("talents priest improved_mind_blast") { }

    //"Reduces the cooldown of your Mind Blast spell by 2.5 sec."
    class ImprovedMindBlastTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            Creature* dummy = SpawnCreature();

            LearnTalent(priest, Talents::Priest::IMPROVED_MIND_BLAST_RNK_5);

            TEST_COOLDOWN(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, Milliseconds(5500));
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedMindBlastTestImpt>();
    }
};

class MindFlayTest : public TestCaseScript
{
public:
    MindFlayTest() : TestCaseScript("talents priest mind_flay") { }

    //"Assault the target's mind with Shadow energy, causing 528 Shadow damage over 3sec and slowing their movement speed by 50%."
    class MindFlayTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            Creature* dummy = SpawnCreature();

            EQUIP_NEW_ITEM(priest, 34336); // Sunflare -- 292 SP

            // Damage
            uint32 const tickAmount = 3;
            float const spellCoeff = ClassSpellsCoeff::Priest::MIND_FLAY;
            uint32 const spellBonus = 292 * spellCoeff / tickAmount;
            uint32 const expectedMindFlayTick = ClassSpellsDamage::Priest::MIND_FLAY_RNK_7_TICK + spellBonus;
            TEST_CHANNEL_DAMAGE(priest, dummy, ClassSpells::Priest::MIND_FLAY_RNK_7, tickAmount, expectedMindFlayTick);

            // Slow
            float const expectedSpeed = dummy->GetSpeed(MOVE_RUN) * 0.5f;
            dummy->AddAura(ClassSpells::Priest::MIND_FLAY_RNK_7, dummy);
            TEST_AURA_MAX_DURATION(dummy, ClassSpells::Priest::MIND_FLAY_RNK_7, Seconds(3));
            ASSERT_INFO("Speed: %f - Expected: %f", dummy->GetSpeed(MOVE_RUN), expectedSpeed);
            TEST_ASSERT(Between(dummy->GetSpeed(MOVE_RUN), expectedSpeed - 0.1f, expectedSpeed + 0.1f));

            // Mana cost
            uint32 const expectedMindFlayMana = 230;
            TEST_POWER_COST(priest, ClassSpells::Priest::MIND_FLAY_RNK_7, POWER_MANA, expectedMindFlayMana);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<MindFlayTestImpt>();
    }
};

class ImprovedFadeTest : public TestCaseScript
{
public:
    ImprovedFadeTest() : TestCaseScript("talents priest improved_fade") { }

    //Decreases the cooldown of your Fade ability by 6 sec.
    class ImprovedFadeTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);

            LearnTalent(priest, Talents::Priest::IMPROVED_FADE_RNK_2);

            TEST_COOLDOWN(priest, priest, ClassSpells::Priest::FADE_RNK_7, Seconds(24)); //down from 30
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedFadeTestImpt>();
    }
};

class ShadowReachTest : public TestCaseScript
{
public:
    ShadowReachTest() : TestCaseScript("talents priest shadow_reach") { }

    //Increases the range of your offensive Shadow spells by 20%
    class ShadowReachTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            EnableCriticals(priest, false);

            LearnTalent(priest, Talents::Priest::SHADOW_REACH_RNK_2);
            float const talentRangeFactor = 1.2f;

            float const expected20yIncrease = 20.0f * talentRangeFactor;
            float const expected30yIncrease = 30.0f * talentRangeFactor;
            float const expected40yIncrease = 40.0f * talentRangeFactor;

            Creature* dummy = SpawnCreature();
            TestPlayer* enemy = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);

            TEST_RANGE(priest, dummy, ClassSpells::Priest::DEVOURING_PLAGUE_RNK_7, expected30yIncrease);
            TEST_RANGE(priest, dummy, ClassSpells::Priest::HEX_OF_WEAKNESS_RNK_7, expected30yIncrease);
            TEST_RANGE(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, expected30yIncrease);
            TEST_RANGE(priest, enemy, ClassSpells::Priest::MIND_CONTROL_RNK_3, expected20yIncrease);
            TEST_RANGE(priest, enemy, ClassSpells::Priest::MIND_FLAY_RNK_7, expected20yIncrease);
            TEST_RANGE(priest, dummy, ClassSpells::Priest::MIND_SOOTHE_RNK_4, expected40yIncrease);

            TEST_RANGE(priest, dummy, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2, expected30yIncrease); //BUG HERE
            TEST_RANGE(priest, dummy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, expected30yIncrease);
            TEST_RANGE(priest, dummy, ClassSpells::Priest::SHADOWFIEND_RNK_1, expected30yIncrease);
            TEST_RANGE(priest, dummy, ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1, expected30yIncrease);
            TEST_RANGE(priest, dummy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, expected30yIncrease);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ShadowReachTestImpt>();
    }
};

class ShadowWeavingTest : public TestCaseScript
{
public:
    /*
    Infos:
        - Hex of Weakness doesnt apply Shadow Weaving: https://youtu.be/vHC5bXVtd0E?t=59s
        - Vampiric Embrace doesnt apply Shadow Weaving: https://youtu.be/Fv_CJDCmGO8?t=1m43s
    */
    ShadowWeavingTest() : TestCaseScript("talents priest shadow_weaving") { }

    /*"Your Shadow damage spells have a 100% chance to cause your target to be vulnerable to Shadow damage.
    This vulnerability increases the Shadow damage dealt to your target by 2% and lasts 15sec. Stacks up to 5 times."*/
    class ShadowWeavingTestImpt : public TestCase
    {
    public:


        uint32 const MAX_STACK = 5;

        void AssertSpellAppliesShadowWeaving(TestPlayer* priest, Creature* dummy, uint32 spellId)
        {
            TEST_HAS_NOT_AURA(dummy, Talents::Priest::SHADOW_WEAVING_RNK_5_PROC);
            FORCE_CAST(priest, dummy, spellId, SPELL_MISS_NONE, TRIGGERED_FULL_MASK);
            WaitNextUpdate();
            ASSERT_INFO("After %s, Dummy doesnt have Shadow Weaving", _SpellString(spellId).c_str());
            TEST_HAS_AURA(dummy, Talents::Priest::SHADOW_WEAVING_RNK_5_PROC);
            dummy->RemoveAurasDueToSpell(spellId);
            dummy->RemoveAurasDueToSpell(Talents::Priest::SHADOW_WEAVING_RNK_5_PROC);
        }

        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Creature* dummy = SpawnCreature();

            LearnTalent(priest, Talents::Priest::SHADOW_WEAVING_RNK_5);
            float const talentShadowDamageFactor = 1.1f; //with 5 stacks

            AssertSpellAppliesShadowWeaving(priest, dummy, ClassSpells::Priest::DEVOURING_PLAGUE_RNK_7); // https://youtu.be/w0CrmZygCuY?t=15m46s
            AssertSpellAppliesShadowWeaving(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11);
            AssertSpellAppliesShadowWeaving(priest, dummy, ClassSpells::Priest::MIND_FLAY_RNK_7);
            AssertSpellAppliesShadowWeaving(priest, dummy, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2);
            AssertSpellAppliesShadowWeaving(priest, dummy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10);
            AssertSpellAppliesShadowWeaving(priest, dummy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3);

            // Max stack + aura duration
            for (uint8 i = 0; i < MAX_STACK * 2; i++) //more than MAX_STACK to make sure stack amount is correctly limited
                FORCE_CAST(priest, dummy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_1, SPELL_MISS_NONE, TRIGGERED_FULL_MASK);
            WaitNextUpdate();
            TEST_AURA_MAX_DURATION(dummy, Talents::Priest::SHADOW_WEAVING_RNK_5_PROC, Seconds(15));
            Aura* aura = dummy->GetAura(Talents::Priest::SHADOW_WEAVING_RNK_5_PROC);
            TEST_ASSERT(aura != nullptr);
            TEST_ASSERT(aura->GetStackAmount() == int32(MAX_STACK));

            // Shadow damage boost
            uint32 const expectedMindFlayTick = ClassSpellsDamage::Priest::MIND_FLAY_RNK_7_TICK * talentShadowDamageFactor;
            TEST_CHANNEL_DAMAGE(priest, dummy, ClassSpells::Priest::MIND_FLAY_RNK_7, 3, expectedMindFlayTick);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ShadowWeavingTestImpt>();
    }
};

class SilenceTest : public TestCaseScript
{
public:
    SilenceTest() : TestCaseScript("talents priest silence") { }

    //"Silences the target, preventing them from casting spells for 5sec."
    class SilenceTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            TestPlayer* enemy = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            TEST_CAST(enemy, enemy, ClassSpells::Priest::GREATER_HEAL_RNK_7);
            FORCE_CAST(priest, enemy, ClassSpells::Priest::SILENCE_RNK_1);
            TEST_AURA_MAX_DURATION(enemy, ClassSpells::Priest::SILENCE_RNK_1, Seconds(5));
            TEST_CAST(enemy, enemy, ClassSpells::Priest::GREATER_HEAL_RNK_7, SPELL_FAILED_PREVENTED_BY_MECHANIC);
            TEST_HAS_COOLDOWN(priest, ClassSpells::Priest::SILENCE_RNK_1, Seconds(45));

            TEST_POWER_COST(priest, ClassSpells::Priest::SILENCE_RNK_1, POWER_MANA, 225);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<SilenceTestImpt>();
    }
};

//"Afflicts your target with Shadow energy that causes all party members to be healed for 15% of any Shadow spell damage you deal for 1min."
class VampiricEmbraceTest : public TestCase
{
public:
    /*
    Bugs:
        - Spiritual Atunnement is not working with VE
    */

    void Test() override
    {
        TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
        TestPlayer* priest2 = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
        TestPlayer* paladin = SpawnPlayer(CLASS_PALADIN, RACE_HUMAN);
        Creature* dummy = SpawnCreature();

        EQUIP_NEW_ITEM(priest, 34335); // Hammer of Sanctification -- 183 SP & 550 BH
        paladin->DisableRegeneration(true);
        paladin->SetHealth(1);
        paladin->SetPower(POWER_MANA, 0);
        GroupPlayer(priest, paladin);
        GroupPlayer(priest, priest2);
        paladin->AddAura(ClassSpells::Paladin::SPIRITUAL_ATTUNEMENT_RNK_2, paladin);
        TEST_ASSERT(paladin->HasAura(ClassSpells::Paladin::SPIRITUAL_ATTUNEMENT_RNK_2));

        TEST_POWER_COST(priest, ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1, POWER_MANA, 52);
        FORCE_CAST(priest, dummy, ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1);
        TEST_AURA_MAX_DURATION(dummy, ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1, Seconds(60));
        TEST_HAS_COOLDOWN(priest, ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1, Seconds(10));

        TriggerCastFlags triggerFlags = TriggerCastFlags(TRIGGERED_FULL_MASK | TRIGGERED_IGNORE_SPEED);
        SECTION("Heal from shadow damage only", [&] {
            FORCE_CAST(priest, dummy, ClassSpells::Priest::SMITE_RNK_10, SPELL_MISS_NONE, triggerFlags);
            FORCE_CAST(priest, dummy, ClassSpells::Mage::FIREBALL_RNK_13, SPELL_MISS_NONE, triggerFlags);
            FORCE_CAST(priest, dummy, ClassSpells::Mage::FROSTBOLT_RNK_13, SPELL_MISS_NONE, triggerFlags);
            FORCE_CAST(priest, dummy, ClassSpells::Mage::ARCANE_EXPLOSION_RNK_8, SPELL_MISS_NONE, triggerFlags);
            FORCE_CAST(priest, dummy, ClassSpells::Shaman::EARTH_SHOCK_RNK_8, SPELL_MISS_NONE, triggerFlags);
            TEST_ASSERT(paladin->GetHealth() == 1);
        });

        SECTION("Healing value", [&] {
            auto AI = _GetCasterAI(priest);
            AI->ResetSpellCounters();
            FORCE_CAST(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, SPELL_MISS_NONE, triggerFlags);
            auto[minDmg, maxDmg] = GetDamagePerSpellsTo(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, {}, 1);
            float const vampiricEmbraceHealFactor = 0.15f;
            uint32 const expectedVEHeal = minDmg * vampiricEmbraceHealFactor;
            uint32 const paladinExpectedHealth = 1 + expectedVEHeal;
            ASSERT_INFO("Paladin has %u HP but %u was expected.", paladin->GetHealth(), paladinExpectedHealth);
            TEST_ASSERT(paladin->GetHealth() == paladinExpectedHealth);
        });

        SECTION("Paladin spiritual attunement giving mana", STATUS_KNOWN_BUG, [&] {
            // Paladin's Spiritual Attunement works with VE heal (http://wowwiki.wikia.com/wiki/Vampiric_Embrace?oldid=1432448)
            // Bug here: no mana given. Reason is that currently VE has SPELL_ATTR3_CANT_TRIGGER_PROC and will not trigger spiritual attunement
            // It's probably more a Spiritual Attunement bug than vampiric embrace bug, we can't remove this attribute from VE. Or SPELL_ATTR3_CANT_TRIGGER_PROC handling is incorrect.
            float const spiritualAttunementFactor = 0.1f;
            auto AI = _GetCasterAI(priest);
            AI->ResetSpellCounters();
            FORCE_CAST(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, SPELL_MISS_NONE, triggerFlags);
            auto[minDmg, maxDmg] = GetDamagePerSpellsTo(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, {}, 1);
            float const vampiricEmbraceHealFactor = 0.15f;
            uint32 const expectedVEHeal = minDmg * vampiricEmbraceHealFactor;
            uint32 const expectedPaladinMana = expectedVEHeal * spiritualAttunementFactor;
            ASSERT_INFO("Paladin has %u MP but %u was expected through Spiritual Attunement.", paladin->GetPower(POWER_MANA), expectedPaladinMana);
            TEST_ASSERT(paladin->GetPower(POWER_MANA) == expectedPaladinMana);
        });

        // Each priest's VE generates healing from their own shadow damage.
        SECTION("Only from your own damage", [&] {
            uint32 paladinStartingHealth = paladin->GetHealth();
            FORCE_CAST(priest2, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, SPELL_MISS_NONE, triggerFlags);
            ASSERT_INFO("Priest2 incorrectly heals through Priest1 Vampiric Embrace.");
            TEST_ASSERT(paladin->GetHealth() == paladinStartingHealth);
        });

        SECTION("Stacks", [&] {
            // 1 VE per priest
            FORCE_CAST(priest2, dummy, ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1);

            uint32 veCount = 0;
            auto& auras = dummy->GetAppliedAuras();
            for (const auto & i : auras)
            {
                if (i.second->GetBase()->GetId() == ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1)
                    veCount++;
            }
            ASSERT_INFO("Dummy only has %u Vampiric Embrace instead of 2.", veCount);
            TEST_ASSERT(veCount == 2);
        });
    }
};

class ImprovedVampiricEmbraceTest : public TestCaseScript
{
public:
    ImprovedVampiricEmbraceTest() : TestCaseScript("talents priest improved_vampiric_embrace") { }

    //"Increases the percentage healed by Vampiric Embrace by an additional 10%"
    class ImprovedVampiricEmbraceTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            Creature* dummy = SpawnCreature();

            priest->DisableRegeneration(true);
            priest->SetHealth(1);

            LearnTalent(priest, Talents::Priest::IMPROVED_VAMPIRIC_EMBRACE_RNK_2);
            float const talentFactor = 0.1f;

            FORCE_CAST(priest, dummy, ClassSpells::Priest::VAMPIRIC_EMBRACE_RNK_1, SPELL_MISS_NONE, TRIGGERED_FULL_MASK);
            FORCE_CAST(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, SPELL_MISS_NONE, TRIGGERED_FULL_MASK);
            auto[minDmg, maxDmg] = GetDamagePerSpellsTo(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, {}, 1);
            float const vampiricEmbraceHealFactor = 0.15f + talentFactor;
            uint32 const expectedVEHeal = minDmg * vampiricEmbraceHealFactor;
            uint32 const priestExpectedHealth = 1 + expectedVEHeal;
            ASSERT_INFO("Priest has %u HP but %u was expected.", priest->GetHealth(), priestExpectedHealth);
            TEST_ASSERT(priest->GetHealth() == priestExpectedHealth);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ImprovedVampiricEmbraceTestImpt>();
    }
};

class FocusedMindTest : public TestCaseScript
{
public:
    FocusedMindTest() : TestCaseScript("talents priest focused_mind") { }

    //Reduces the mana cost of your Mind Blast, Mind Control and Mind Flay spells by 15%.
    class FocusedMindTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);

            LearnTalent(priest, Talents::Priest::FOCUSED_MIND_RNK_3);
            float const talentManaFactor = 1 - 0.15f;

            uint32 const expectedMindBlastManaCost = 450 * talentManaFactor;
            uint32 const expectedMindControlManaCost = 750 * talentManaFactor;
            uint32 const expectedMindFlayManaCost = 230 * talentManaFactor;

            TEST_POWER_COST(priest, ClassSpells::Priest::MIND_BLAST_RNK_11, POWER_MANA, expectedMindBlastManaCost);
            TEST_POWER_COST(priest, ClassSpells::Priest::MIND_CONTROL_RNK_3, POWER_MANA, expectedMindControlManaCost);
            TEST_POWER_COST(priest, ClassSpells::Priest::MIND_FLAY_RNK_7, POWER_MANA, expectedMindFlayManaCost);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<FocusedMindTestImpt>();
    }
};

class ShadowResilienceTest : public TestCaseScript
{
public:
    ShadowResilienceTest() : TestCaseScript("talents priest shadow_resilience") { }

    //""Reduces the chance you'll be critically hit by all spells by 4%."
    class ShadowResilienceTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            TestPlayer* enemy = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);

            LearnTalent(priest, Talents::Priest::SHADOW_RESILIENCE_RNK_2);
            float const talentCritFactor = 4.f;

            EQUIP_NEW_ITEM(enemy, 34182); // Some crit spell "Improves spell critical strike rating by 49."
            float const expectedCritChance = enemy->GetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + SPELL_SCHOOL_SHADOW) - talentCritFactor;

            TEST_SPELL_CRIT_CHANCE(enemy, priest, ClassSpells::Priest::MIND_BLAST_RNK_11, expectedCritChance);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ShadowResilienceTestImpt>();
    }
};

class DarknessTest : public TestCaseScript
{
public:
    DarknessTest() : TestCaseScript("talents priest darkness") { }

    //"Increases your Shadow spell damage by 10 %"
    class DarknessTestImpt : public TestCase
    {
        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Creature* dummy = SpawnCreature();

            LearnTalent(priest, Talents::Priest::DARKNESS_RNK_5);
            float const talentDamageFactor = 1.1f;

            // MB
            uint32 const mindBlastMin = ClassSpellsDamage::Priest::MIND_BLAST_RNK_11_MIN * talentDamageFactor;
            uint32 const mindBlastMax = ClassSpellsDamage::Priest::MIND_BLAST_RNK_11_MAX * talentDamageFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, mindBlastMin, mindBlastMax, false);

            // MF
            uint32 const expectedMindFlayTick = ClassSpellsDamage::Priest::MIND_FLAY_RNK_7_TICK * talentDamageFactor;
            TEST_CHANNEL_DAMAGE(priest, dummy, ClassSpells::Priest::MIND_FLAY_RNK_7, 3, expectedMindFlayTick);

            // SwD
            uint32 const shadowWordDeathMin = ClassSpellsDamage::Priest::SHADOW_WORD_DEATH_RNK_2_MIN * talentDamageFactor;
            uint32 const shadowWordDeathMax = ClassSpellsDamage::Priest::SHADOW_WORD_DEATH_RNK_2_MAX * talentDamageFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2, shadowWordDeathMin, shadowWordDeathMax, false);

            // SwP
            uint32 const shadowWordPainTotal = 6 * floor(ClassSpellsDamage::Priest::SHADOW_WORD_PAIN_RNK_10_TICK * talentDamageFactor);
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, shadowWordPainTotal, false);

            // VT 
            uint32 const vampiricTouchTotal = 5 * floor(ClassSpellsDamage::Priest::VAMPIRIC_TOUCH_RNK_3_TICK * talentDamageFactor);
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, vampiricTouchTotal, false);

            // Devouring Plague
            uint32 const expectedDPDoT = 8 * floor(ClassSpellsDamage::Priest::DEVOURING_PLAGUE_RNK_7_TICK * talentDamageFactor);
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::DEVOURING_PLAGUE_RNK_7, expectedDPDoT, false);

            SECTION("Shadowguard", STATUS_WIP, [&] {
                //TODO
            });
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<DarknessTestImpt>();
    }
};

class ShadowformTest : public TestCaseScript
{
public:
    ShadowformTest() : TestCaseScript("talents priest shadowform") { }
    
    //Assume a Shadowform, increasing your Shadow damage by 15% and reducing Physical damage done to you by 15%. However, you may not cast Holy spells while in this form.
    class ShadowformTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            TestPlayer* shaman = SpawnPlayer(CLASS_SHAMAN, RACE_DRAENEI);
            Creature* dummy = SpawnCreature();

            TEST_CAST(priest, priest, ClassSpells::Priest::SHADOWFORM_RNK_1);
            TEST_HAS_AURA(priest, ClassSpells::Priest::SHADOWFORM_RNK_1);
            float const talentShadowDamageFactor = 1.15f;
            float const talentPhysicalDamageFactor = 1 - 0.15f;

            // Physical Damage taken
            EQUIP_NEW_ITEM(shaman, 34165); // Fang of Kalecgos
            WaitNextUpdate();
            auto[minMelee, maxMelee] = CalcMeleeDamage(shaman, priest, BASE_ATTACK);
            uint32 const expectedMinMelee = minMelee * talentPhysicalDamageFactor;
            uint32 const expectedMaxMelee = maxMelee * talentPhysicalDamageFactor;
            TEST_MELEE_DAMAGE(shaman, priest, BASE_ATTACK, expectedMinMelee, expectedMaxMelee, false);

            // MB
            uint32 const mindBlastMin = ClassSpellsDamage::Priest::MIND_BLAST_RNK_11_MIN * talentShadowDamageFactor;
            uint32 const mindBlastMax = ClassSpellsDamage::Priest::MIND_BLAST_RNK_11_MAX * talentShadowDamageFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, mindBlastMin, mindBlastMax, false);

            // MF
            uint32 const expectedMindFlayTick = ClassSpellsDamage::Priest::MIND_FLAY_RNK_7_TICK * talentShadowDamageFactor;
            TEST_CHANNEL_DAMAGE(priest, dummy, ClassSpells::Priest::MIND_FLAY_RNK_7, 3, expectedMindFlayTick);

            // SwD
            uint32 const shadowWordDeathMin = ClassSpellsDamage::Priest::SHADOW_WORD_DEATH_RNK_2_MIN * talentShadowDamageFactor;
            uint32 const shadowWordDeathMax = ClassSpellsDamage::Priest::SHADOW_WORD_DEATH_RNK_2_MAX * talentShadowDamageFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2, shadowWordDeathMin, shadowWordDeathMax, false);

            // SwP
            uint32 const shadowWordPainTotal = 6 * floor(ClassSpellsDamage::Priest::SHADOW_WORD_PAIN_RNK_10_TICK * talentShadowDamageFactor);
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, shadowWordPainTotal, false);

            // VT
            uint32 const vampiricTouchTotal = 5 * floor(ClassSpellsDamage::Priest::VAMPIRIC_TOUCH_RNK_3_TICK * talentShadowDamageFactor);
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, vampiricTouchTotal, false);

            // Holy spell remove Shadowform - Client actually removes shadowform by requesting cancel. We need to make sure we can't bypass this though.
            TEST_CAST(priest, priest, ClassSpells::Priest::RENEW_RNK_12, SPELL_FAILED_NOT_SHAPESHIFT);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ShadowformTestImpt>();
    }
};

class ShadowPowerTest : public TestCaseScript
{
public:
    ShadowPowerTest() : TestCaseScript("talents priest shadow_power") { }

    //"Increases the critical strike chance of your Mind Blast and Shadow Word : Death spells by 15%"
    class ShadowPowerTestImpt : public TestCase
    {
    public:


        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_HUMAN);
            Creature* dummy = SpawnCreature();

            LearnTalent(priest, Talents::Priest::SHADOW_POWER_RNK_5);
            float const talentCritFactor = 15.f;

            float const expectedCritChance = priest->GetFloatValue(PLAYER_SPELL_CRIT_PERCENTAGE1 + SPELL_SCHOOL_SHADOW) + talentCritFactor;

            TEST_SPELL_CRIT_CHANCE(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, expectedCritChance);
            TEST_SPELL_CRIT_CHANCE(priest, dummy, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2, expectedCritChance);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<ShadowPowerTestImpt>();
    }
};

class MiseryTest : public TestCaseScript
{
public:
    MiseryTest() : TestCaseScript("talents priest misery") { }

    /* "Your Shadow Word: Pain, Mind Flay and Vampiric Touch spells also cause the target to take an additional 5% spell damage."
    We assume Misery cannot be resisted, resist being already rolled on the spell proccing it
    */
    class MiseryTestImpt : public TestCase
    {
    public:


        void AssertSpellAppliesMisery(TestPlayer* priest, Creature* dummy, uint32 spellId, Seconds durationTime, uint32 castTime = 0)
        {
            TEST_HAS_NOT_AURA(dummy, Talents::Priest::MISERY_RNK_5_TRIGGER);
            FORCE_CAST(priest, dummy, spellId);
            if (castTime)
            {
                _StartUnitChannels(priest);
                Wait(castTime);
            }
            WaitNextUpdate();
            TEST_HAS_AURA(dummy, spellId);
            ASSERT_INFO("After spell %s, Dummy doesnt have Misery.", _SpellString(spellId).c_str());
            TEST_AURA_MAX_DURATION(dummy, Talents::Priest::MISERY_RNK_5_TRIGGER, Seconds(24));
            dummy->RemoveAurasDueToSpell(Talents::Priest::MISERY_RNK_5_TRIGGER);
            dummy->RemoveAurasDueToSpell(spellId);
            Wait(1500); // GCD
        }

        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Creature* dummy = SpawnCreature();

            LearnTalent(priest, Talents::Priest::MISERY_RNK_5);
            float const talentDamageFactor = 1.05f;

            AssertSpellAppliesMisery(priest, dummy, ClassSpells::Priest::MIND_FLAY_RNK_7, Seconds(3));
            AssertSpellAppliesMisery(priest, dummy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, Seconds(18));
            AssertSpellAppliesMisery(priest, dummy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, Seconds(15), 1500);

            // Increases spell damage by 5%
            dummy->AddAura(Talents::Priest::MISERY_RNK_5_TRIGGER, dummy);
            uint32 const mindBlastMin = ClassSpellsDamage::Priest::MIND_BLAST_RNK_11_MIN * talentDamageFactor;
            uint32 const mindBlastMax = ClassSpellsDamage::Priest::MIND_BLAST_RNK_11_MAX * talentDamageFactor;
            TEST_DIRECT_SPELL_DAMAGE(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11, mindBlastMin, mindBlastMax, false);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<MiseryTestImpt>();
    }
};

class VampiricTouchTest : public TestCaseScript
{
public:
    VampiricTouchTest() : TestCaseScript("talents priest vampiric_touch") { }

    //"Causes 650 Shadow damage over 15sec to your target and causes all party members to gain mana equal to 5% of any Shadow spell damage you deal."
    class VampiricTouchTestImpt : public TestCase
    {
    public:


        float const restoreManaFactor = 0.05f;

        // Check if the spell restores some mana through VT. If DoT, wait for its first tick otherwise resolves instantly.
        void AssertSpellRestoresMana(TestPlayer* priest, Creature* dummy, uint32 spellId, bool dot = false)
        {
            auto AI = _GetCasterAI(priest);
            AI->ResetSpellCounters();
            bool isVT = (spellId == ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3);
            if (!isVT)
                FORCE_CAST(priest, dummy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, SPELL_MISS_NONE, TRIGGERED_FULL_MASK);
            priest->SetPower(POWER_MANA, 0);
            priest->DisableRegeneration(true);

            FORCE_CAST(priest, dummy, spellId, SPELL_MISS_NONE, TriggerCastFlags(TRIGGERED_CAST_DIRECTLY | TRIGGERED_IGNORE_POWER_AND_REAGENT_COST | TRIGGERED_TREAT_AS_NON_TRIGGERED));
            WaitNextUpdate(); //make sure channel started if any
            uint32 expectedManaRestored = 0;
            if (dot)
            {
                Wait(3000); //wait for a/some tick
                auto[dotDamageToTarget, tickCount] = AI->GetDotDamage(dummy, spellId);
                TEST_ASSERT(dotDamageToTarget != 0);
                if (isVT) //ignore VT damage in this case, we want to count it only once
                    dotDamageToTarget = 0;
                uint32 manaRestoredPerTick = std::floor((dotDamageToTarget / tickCount) * restoreManaFactor) * tickCount;
                auto[vtDamage, vtTickCount] = AI->GetDotDamage(dummy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3);
                expectedManaRestored = manaRestoredPerTick + std::floor(vtDamage * restoreManaFactor);
            }
            else
            {
                auto[minDmg, maxDmg] = GetDamagePerSpellsTo(priest, dummy, spellId, {}, 1);
                expectedManaRestored += minDmg * restoreManaFactor;
            }
            ASSERT_INFO("Spell %u doesnt restore any mana.", spellId);
            TEST_ASSERT(expectedManaRestored != 0);
            ASSERT_INFO("After spell %u, Priest has %u MP but %u was expected.", spellId, priest->GetPower(POWER_MANA), expectedManaRestored);
            TEST_ASSERT(priest->GetPower(POWER_MANA) == expectedManaRestored);
            dummy->RemoveAurasDueToSpell(ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3);
            dummy->RemoveAurasDueToSpell(spellId);
        }

        void Test() override
        {
            TestPlayer* priest = SpawnPlayer(CLASS_PRIEST, RACE_BLOODELF);
            Creature* dummy = SpawnCreature();

            EQUIP_NEW_ITEM(priest, 34336); // Sunflare -- 292 SP
            
            uint32 const expectedVampiriTouchManaCost = 425;

            TEST_POWER_COST(priest, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, POWER_MANA, expectedVampiriTouchManaCost);

            // Damage
            float const tickAmount = 5.f;
            float const spellCoeff = ClassSpellsCoeff::Priest::VAMPIRIC_TOUCH;
            uint32 const spellBonus = 292 * spellCoeff / tickAmount;
            uint32 const expectedTickDamage = ClassSpellsDamage::Priest::VAMPIRIC_TOUCH_RNK_3_TICK + spellBonus;
            //uint32 const manaRestoredPerVTTick = expectedTickDamage * restoreManaFactor;
            float const expectedVampiricTouchTotal = tickAmount * expectedTickDamage;
            TEST_DOT_DAMAGE(priest, dummy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, expectedVampiricTouchTotal, false);

            // Mana restored - Only wait for 3s tick to assert it gives back mana
            RemoveAllEquipedItems(priest); // Unequip weapon to remove spell power from calculations
            // VT
            AssertSpellRestoresMana(priest, dummy, ClassSpells::Priest::VAMPIRIC_TOUCH_RNK_3, true);
            // MB
            AssertSpellRestoresMana(priest, dummy, ClassSpells::Priest::MIND_BLAST_RNK_11);
            // MF
            AssertSpellRestoresMana(priest, dummy, ClassSpells::Priest::MIND_FLAY_RNK_7, true);
            // SwD
            AssertSpellRestoresMana(priest, dummy, ClassSpells::Priest::SHADOW_WORD_DEATH_RNK_2);
            // SwP
            AssertSpellRestoresMana(priest, dummy, ClassSpells::Priest::SHADOW_WORD_PAIN_RNK_10, true);
            // DP
            AssertSpellRestoresMana(priest, dummy, ClassSpells::Priest::DEVOURING_PLAGUE_RNK_7, true);
        }
    };

    std::unique_ptr<TestCase> GetTest() const override
    {
        return std::make_unique<VampiricTouchTestImpt>();
    }
};

void AddSC_test_talents_priest()
{
    // Discipline
    new UnbreakableWillTest();
    new PriestWandSpecializationTest();
    new SilentResolve();
    new ImprovedPowerWordFortitudeTest();
    new ImprovedPowerWordShieldTest();
    new MartyrdomTest();
    new AbsolutionTest();
    new InnerFocusTest();
    new MeditationTest();
    new ImprovedInnerFireTest();
    new MentalAgilityTest();
    new ImprovedManaBurnTest();
    new MentalStrengthTest();
    new DivineSpiritTest();
    new ImprovedDivineSpiritTest();
    new FocusedPowerTest();
    new ForceOfWillTest();
    new FocusedWillTest();
    new PowerInfusionTest();
    new ReflectiveShieldTest();
    new EnlightenmentTest();
    new PainSuppressionTest();
    // Holy
    new HealingFocusTest();
    new ImprovedRenewTest();
    new HolySpecializationTest();
    new SpellWardingTest();
    new DivineFuryTest();
    new HolyNovaTest();
    new BlessedRecoveryTest();
    new InspirationTest();
    new HolyReachTest();
    new ImprovedHealingTest();
    new SearingLightTest();
    new HealingPrayersTest();
    new SpiritOfRedemptionTest();
    new SpiritualGuidanceTest();
    new SurgeOfLightTest();
    new SpiritualHealingTest();
    new HolyConcentrationTest();
    // TODO: Lightwell
    new BlessedResilienceTest();
    new EmpoweredHealingTest();
    new CircleOfHealingTest();
    // Shadow
    new SpiritTapTest();
    new BlackoutTest();
    new ShadowAffinityTest();
    new ImprovedShadowWordPainTest();
    new ShadowFocusTest();
    new ImprovedPsychicScreamTest();
    new ImprovedMindBlastTest();
    new MindFlayTest();
    new ImprovedFadeTest();
    new ShadowReachTest();
    new ShadowWeavingTest();
    new SilenceTest();
    RegisterTestCase("talents priest vampiric_embrace", VampiricEmbraceTest);
    new ImprovedVampiricEmbraceTest();
    new FocusedMindTest();
    new ShadowResilienceTest();
    new DarknessTest();
    new ShadowformTest();
    new ShadowPowerTest();
    new MiseryTest();
    new VampiricTouchTest();
}
